diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..9d68dd25 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.go/ +.git/ +obj-x86_64-linux-gnu/ +obj-aarch64-linux-gnu/ +obj-arm-linux-gnueabihf/ +obj-i686-linux-gnu/ +unit.out +aptly.test +build/ +dpkgs/ diff --git a/.flake8 b/.flake8 index 607808a1..393ab659 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] -max-line-length = 200 +max-line-length = 240 ignore = E126,E241,E741,W504 include = system exclude = - system/env \ No newline at end of file + system/env diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0da3c1d3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: aptly-dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a58ddb7..8085c6e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,3 @@ -# Based on https://github.com/aptly-dev/aptly/blob/master/.travis.yml - name: CI on: @@ -13,32 +11,17 @@ on: defaults: run: # see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell - shell: bash --noprofile --norc -eo pipefail -x {0} + shell: bash --noprofile --norc -eo pipefail {0} env: DEBIAN_FRONTEND: noninteractive jobs: - build: - name: test - runs-on: ubuntu-20.04 - continue-on-error: ${{ matrix.allow_failure }} + test: + name: "Test (Ubuntu 22.04)" + runs-on: ubuntu-22.04 + continue-on-error: false timeout-minutes: 30 - strategy: - fail-fast: true - matrix: - go: [1.16, 1.17, 1.18] - run_long_tests: [no] - allow_failure: [false] - include: - # Disable this for now as it's not clear how to select the latest master branch - # version of go using actions/setup-go@v2. - # - go: master - # run_long_tests: no - # allow_failure: true - - go: 1.17 - run_long_tests: yes - allow_failure: false env: NO_FTP_ACCESS: yes @@ -47,101 +30,256 @@ jobs: GOPROXY: "https://proxy.golang.org" steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: v1.45.0 - - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - - name: Install O/S packages + - name: "Install packages" run: | sudo apt-get update - sudo apt-get install -y graphviz gnupg1 gnupg2 gpgv1 gpgv2 git gcc make + 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 - - name: Install Python packages + - name: "Checkout repository" + uses: actions/checkout@v4 + with: + # fetch the whole repo for `git describe` to work + fetch-depth: 0 + + - name: "Run flake8" run: | - pip install six packaging appdirs virtualenv - pip install -U pip setuptools - pip install -r system/requirements.txt + make flake8 - - name: Install Azurite + - name: "Read go version from go.mod" + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: "Setup Go" + uses: actions/setup-go@v3 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + + - name: "Install Azurite" id: azuright uses: potatoqualitee/azuright@v1.1 with: directory: ${{ runner.temp }} - - name: Get aptly version - run: | - make version - - - name: Make + - name: "Run Unit Tests" env: - RUN_LONG_TESTS: ${{ matrix.run_long_tests }} - AZURE_STORAGE_ENDPOINT: "127.0.0.1:10000" + RUN_LONG_TESTS: 'yes' + AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1" AZURE_STORAGE_ACCOUNT: "devstoreaccount1" AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | - make + sudo mkdir -p /srv ; sudo chown runner /srv + COVERAGE_DIR=${{ runner.temp }} make test - - name: Upload code coverage - if: matrix.run_long_tests + - name: "Run Benchmark" + run: | + COVERAGE_DIR=${{ runner.temp }} make bench + + - name: "Run System Tests" + env: + RUN_LONG_TESTS: 'yes' + AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1" + AZURE_STORAGE_ACCOUNT: "devstoreaccount1" + AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + sudo mkdir -p /srv ; sudo chown runner /srv + COVERAGE_DIR=${{ runner.temp }} make system-test + + - name: "Merge code coverage" + run: | + go install github.com/wadey/gocovmerge@latest + ~/go/bin/gocovmerge unit.out ${{ runner.temp }}/*.out > coverage.txt + + - name: "Upload code coverage" uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.txt - release: - name: release - needs: build - runs-on: ubuntu-20.04 + ci-debian-build: + name: "Build" + needs: test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + name: ["Debian 13/testing", "Debian 12/bookworm", "Debian 11/bullseye", "Debian 10/buster", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"] + arch: ["amd64", "i386" , "arm64" , "armhf"] + include: + - name: "Debian 13/testing" + suite: trixie + image: debian:trixie-slim + - name: "Debian 12/bookworm" + suite: bookworm + image: debian:bookworm-slim + - name: "Debian 11/bullseye" + suite: bullseye + image: debian:bullseye-slim + - name: "Debian 10/buster" + suite: buster + image: debian:buster-slim + - name: "Ubuntu 24.04" + suite: noble + image: ubuntu:24.04 + - name: "Ubuntu 22.04" + suite: jammy + image: ubuntu:22.04 + - name: "Ubuntu 20.04" + suite: focal + image: ubuntu:20.04 + container: + image: ${{ matrix.image }} + env: + APT_LISTCHANGES_FRONTEND: none + DEBIAN_FRONTEND: noninteractive steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: "Install packages" + run: | + apt-get update + apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang jq bash-completion lintian \ + binutils-i686-linux-gnu binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf \ + libc6-dev-i386-cross libc6-dev-armhf-cross libc6-dev-arm64-cross \ + gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu + git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: "Checkout repository" + uses: actions/checkout@v4 with: - # fetch the whole repot for `git describe` to - # work and get the nightly verion + # fetch the whole repo for `git describe` to work fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v2 + - name: "Read go version from go.mod" + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion - - name: Make Release + - name: "Setup Go" + uses: actions/setup-go@v3 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + + - name: "Ensure CI build" + if: github.ref == 'refs/heads/master' + run: | + echo "FORCE_CI=true" >> $GITHUB_OUTPUT + id: force_ci + + - name: "Build Debian packages" + env: + FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} + run: | + make dpkg DEBARCH=${{ matrix.arch }} + + - name: "Check aptly credentials" + env: + APTLY_USER: ${{ secrets.APTLY_USER }} + APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} + run: | + found=no + if [ -n "$APTLY_USER" ] && [ -n "$APTLY_PASSWORD" ]; then + found=yes + fi + echo "Aptly credentials available: $found" + echo "FOUND=$found" >> $GITHUB_OUTPUT + id: aptlycreds + + - name: "Publish CI release to aptly" + if: github.ref == 'refs/heads/master' && steps.aptlycreds.outputs.FOUND == 'yes' + env: + APTLY_USER: ${{ secrets.APTLY_USER }} + APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} + run: | + .github/workflows/scripts/upload-artifacts.sh ci ${{ matrix.suite }} + + - name: "Publish release to aptly" + if: startsWith(github.event.ref, 'refs/tags') && steps.aptlycreds.outputs.FOUND == 'yes' + env: + APTLY_USER: ${{ secrets.APTLY_USER }} + APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} + run: | + .github/workflows/scripts/upload-artifacts.sh release ${{ matrix.suite }} + + ci-binary-build: + name: "Build" + needs: test + runs-on: ubuntu-latest + strategy: + matrix: + goos: [linux, freebsd, darwin] + goarch: ["386", "amd64", "arm", "arm64"] + exclude: + - goos: darwin + goarch: 386 + - goos: darwin + goarch: arm + steps: + - name: "Checkout repository" + uses: actions/checkout@v4 + with: + # fetch the whole repo for `git describe` to work + fetch-depth: 0 + + - name: "Read go version from go.mod" + run: | + echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT + id: goversion + + - name: "Setup Go" + uses: actions/setup-go@v3 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + + - name: "Ensure CI build" + if: github.ref == 'refs/heads/master' + run: | + echo "FORCE_CI=true" >> $GITHUB_OUTPUT + id: force_ci + + - name: "Get aptly version" + env: + FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} + run: | + aptlyver=$(make -s version) + echo "Aptly Version: $aptlyver" + echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT + id: releaseversion + + - name: "Build aptly ${{ matrix.goos }}/${{ matrix.goarch }}" env: GOBIN: /usr/local/bin + FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} run: | - make release + make binaries GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} - - name: Publish nightly release to aptly - if: github.ref == 'refs/heads/master' - env: - APTLY_USER: ${{ secrets.APTLY_USER }} - APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} - run: | - ./upload-artifacts.sh nightly - - - name: Publish release to aptly + - name: "Upload Artifacts" + uses: actions/upload-artifact@v4 if: startsWith(github.event.ref, 'refs/tags') - env: - APTLY_USER: ${{ secrets.APTLY_USER }} - APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} - run: | - ./upload-artifacts.sh release - - - name: Upload artifacts to GitHub Release - if: startsWith(github.event.ref, 'refs/tags') - uses: softprops/action-gh-release@v1 with: - body: Release ${{ github.ref }} generated by the CI. - files: build/* + name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }} + path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + compression-level: 0 # no compression + + gh-release: + name: "Github Release" + runs-on: ubuntu-latest + continue-on-error: false + needs: ci-binary-build + if: startsWith(github.event.ref, 'refs/tags') + steps: + - name: "Download Artifacts" + uses: actions/download-artifact@v4 + with: + path: out/ + + - name: "Release" + uses: softprops/action-gh-release@v2 + with: + files: "out/**/aptly_*.zip" diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 00000000..8da4335a --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,72 @@ +name: golangci-lint +on: + push: + branches: + - master + pull_request: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: "Read go version from go.mod" + run: | + echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT + id: goversion + + - name: "Setup Go" + uses: actions/setup-go@v3 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + + - name: Create VERSION file + run: | + make -s version | tr -d '\n' > VERSION + shell: sh + + - name: Install and initialize swagger + run: | + go install github.com/swaggo/swag/cmd/swag@latest + swag init -q --markdownFiles docs + shell: sh + + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + # Require: The version of golangci-lint to use. + # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. + # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. + version: v1.54.1 + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # + # Note: By default, the `.golangci.yml` file should be at the root of the repository. + # The location of the configuration file can be changed by using `--config=` + # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + # Optional: if set to true, then all caching functionality will be completely disabled, + # takes precedence over all other caching options. + # skip-cache: true + + # Optional: if set to true, then the action won't cache or restore ~/go/pkg. + # skip-pkg-cache: true + + # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. + # skip-build-cache: true + + # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. + # install-mode: "goinstall" diff --git a/.github/workflows/scripts/upload-artifacts.sh b/.github/workflows/scripts/upload-artifacts.sh new file mode 100755 index 00000000..e81f2e0b --- /dev/null +++ b/.github/workflows/scripts/upload-artifacts.sh @@ -0,0 +1,167 @@ +#!/bin/sh + +set -e + +builds=build/ +packages=${builds}*.deb +folder=`mktemp -u tmp.XXXXXXXXXXXXXXX` +aptly_user="$APTLY_USER" +aptly_password="$APTLY_PASSWORD" +aptly_api="https://aptly-ops.aptly.info" +version=`make version` + +action=$1 +dist=$2 + +usage() { + echo "Usage: $0 ci buster|bullseye|bookworm|focal|jammy|noble" >&2 + echo " $0 release" >&2 +} + +# repos and publish must be created beforehand: +#!/bin/sh +#for dist in buster bullseye bookworm focal jammy noble +#do +# for build in ci release +# do +# echo +# echo "# Creating and publishing $build/$dist" +# aptly repo create -distribution=$dist -component=main aptly-$build-$dist +# aptly publish repo -multi-dist -architectures="amd64,i386,arm64,armhf" -acquire-by-hash -component=main \ +# -distribution=$dist -batch -keyring=aptly.pub \ +# aptly-$build-$dist \ +# s3:repo.aptly.info:$build +# done +#done + +if [ -z "$action" ]; then + usage + exit 1 +fi + +if [ "action" = "ci" ] && [ -z "$dist" ]; then + usage + exit 1 +fi + +if [ -z "$aptly_user" ] || [ -z "$aptly_password" ]; then + usage + echo Error: please set APTLY_USER and APTLY_PASSWORD + exit 1 +fi + +echo "Publishing version '$version' to $action for $dist...\n" + +upload() +{ + echo "\nUploading files:" + for file in $packages; do + echo " - $file" + jsonret=`curl -fsS -X POST -F "file=@$file" -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder` + done +} + +cleanup() { + echo "\nCleanup..." + jsonret=`curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder` +} +trap cleanup EXIT + +wait_task() +{ + _id=$1 + _success=0 + for t in `seq 180` + do + jsonret=`curl -fsS -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_id` + _state=`echo $jsonret | jq .State` + if [ "$_state" = "2" ]; then + _success=1 + curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_id + break + fi + if [ "$_state" = "3" ]; then + echo Error: task failed + return 1 + fi + sleep 1 + done + if [ "$_success" -ne 1 ]; then + echo Error: task timeout + return 1 + fi + return 0 +} + +add_packages() { + _aptly_repository=$1 + _folder=$2 + jsonret=`curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$_aptly_repository/file/$_folder?_async=true` + _task_id=`echo $jsonret | jq .ID` + wait_task $_task_id + if [ "$?" -ne 0 ]; then + echo "Error: adding packages to $_aptly_repository failed" + exit 1 + fi +} + +update_publish() { + _publish=$1 + _dist=$2 + jsonret=`curl -fsS -X PUT -H 'Content-Type: application/json' --data \ + '{"AcquireByHash": true, "MultiDist": true, + "Signing": {"Batch": true, "Keyring": "aptly.repo/aptly.pub", "secretKeyring": "aptly.repo/aptly.sec", "PassphraseFile": "aptly.repo/passphrase"}}' \ + -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$_publish/$_dist?_async=true` + _task_id=`echo $jsonret | jq .ID` + wait_task $_task_id + if [ "$?" -ne 0 ]; then + echo "Error: publish failed" + exit 1 + fi +} + +if [ "$action" = "ci" ]; then + if echo "$version" | grep -vq "+"; then + # skip ci when on release tag + exit 0 + fi + + aptly_repository=aptly-ci-$dist + aptly_published=s3:repo.aptly.info:ci + +elif [ "$action" = "release" ]; then + aptly_repository=aptly-release-$dist + aptly_published=s3:repo.aptly.info:release +fi + +upload + +echo "\nAdding packages to $aptly_repository ..." +add_packages $aptly_repository $folder + +echo "\nUpdating published repo $aptly_published ..." +update_publish $aptly_published $dist + +# if [ "$action" = "OBSOLETErelease" ]; then +# aptly_repository=aptly-release +# aptly_snapshot=aptly-$version +# aptly_published=s3:repo.aptly.info:./squeeze +# +# echo "\nAdding packages to $aptly_repository..." +# curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder +# echo +# +# echo "\nCreating snapshot $aptly_snapshot from repo $aptly_repository..." +# curl -fsS -X POST -u $aptly_user:$aptly_password -H 'Content-Type: application/json' --data \ +# "{\"Name\":\"$aptly_snapshot\"}" ${aptly_api}/api/repos/$aptly_repository/snapshots +# echo +# +# echo "\nSwitching published repo $aptly_published to use snapshot $aptly_snapshot..." +# curl -fsS -X PUT -H 'Content-Type: application/json' --data \ +# "{\"AcquireByHash\": true, \"Snapshots\": [{\"Component\": \"main\", \"Name\": \"$aptly_snapshot\"}], +# \"Signing\": {\"Batch\": true, \"Keyring\": \"aptly.repo/aptly.pub\", +# \"secretKeyring\": \"aptly.repo/aptly.sec\", \"PassphraseFile\": \"aptly.repo/passphrase\"}}" \ +# -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$aptly_published +# echo +# fi + diff --git a/.gitignore b/.gitignore index 347cbc1d..f2333751 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,14 @@ *.o *.a *.so +unit.out # Folders _obj _test +tmp/ + # Architecture specific extensions/prefixes *.[568vq] [568vq].out @@ -32,13 +35,42 @@ root/ man/aptly.1.html man/aptly.1.ronn -.goxc.local.json - system/env/ # created by make build for release artifacts +VERSION +aptly.test + build/ -pgp/keyrings/aptly2*.gpg -pgp/keyrings/aptly2*.gpg~ -pgp/keyrings/.#* +system/files/aptly2.gpg~ +system/files/aptly2_passphrase.gpg~ + +*.creds + +.go/ +obj-x86_64-linux-gnu/ +obj-aarch64-linux-gnu/ +obj-arm-linux-gnueabihf/ +obj-i686-linux-gnu/ + +# debian +debian/.debhelper/ +debian/aptly.substvars +debian/aptly/ +debian/debhelper-build-stamp +debian/files +debian/aptly-api/ +debian/*.debhelper +debian/*.debhelper.log +debian/aptly-api.substvars +debian/aptly-dbg.substvars +debian/aptly-dbg/ +usr/bin/aptly +dpkgs/ +debian/changelog.dpkg-bak + +docs/docs.go +docs/swagger.json +docs/swagger.yaml +docs/swagger.conf diff --git a/.golangci.yml b/.golangci.yml index 60d5a032..a8060193 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,6 @@ run: linters: disable-all: true enable: - - deadcode - goconst - gofmt - goimports @@ -14,6 +13,4 @@ linters: - misspell - revive - staticcheck - - structcheck - - varcheck - vetshadow diff --git a/.goxc.json b/.goxc.json deleted file mode 100644 index 60f1940c..00000000 --- a/.goxc.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "AppName": "aptly", - "ArtifactsDest": "xc-out/", - "TasksExclude": [ - "rmbin", - "go-test", - "go-vet" - ], - "TasksAppend": [ - "bintray" - ], - "TaskSettings": { - "debs": { - "metadata": { - "maintainer": "Andrey Smirnov", - "maintainer-email": "me@smira.ru", - "description": "Debian repository management tool" - }, - "metadata-deb": { - "License": "MIT", - "Homepage": "https://www.aptly.info/", - "Depends": "bzip2, xz-utils, gnupg, gpgv", - "Suggests": "graphviz" - }, - "other-mapped-files": { - "/": "root/" - } - }, - "bintray": { - "repository": "aptly", - "subject": "smira", - "package": "aptly", - "downloadspage": "bintray.md" - } - }, - "ResourcesInclude": "README.rst,LICENSE,AUTHORS,man/aptly.1", - "BuildConstraints": "linux,386 linux,amd64 darwin,amd64 freebsd,386 freebsd,amd64", - "MainDirsExclude": "_man,vendor", - "BuildSettings": { - "LdFlagsXVars": { - "Version": "main.Version" - } - }, - "ConfigVersion": "0.9" -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 574a8f4e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,106 +0,0 @@ -dist: xenial -sudo: required - -language: go - -go_import_path: github.com/aptly-dev/aptly - -addons: - apt: - packages: - - python-virtualenv - - graphviz - - gnupg2 - - gpgv2 - -env: - global: - - secure: "EcCzJsqQ3HnIkprBPS1YHErsETcb7KQFBYEzVDE7RYDApWeapLq+r/twMtWMd/fkGeLzr3kWSg7nhSadeHMLYeMl9j+U7ncC5CWG5NMBOj/jowlb9cMCCDlmzMoZLAgR6jm1cJyrWCLsWVlv+D0ZiB0fx4xaBZP/gIr9g6nEwC8=" - - secure: "OxiVNmre2JzUszwPNNilKDgIqtfX2gnRSsVz6nuySB1uO2yQsOQmKWJ9cVYgH2IB5H8eWXKOhexcSE28kz6TPLRuEcU9fnqKY3uEkdwm7rJfz9lf+7C4bJEUdA1OIzJppjnWUiXxD7CEPL1DlnMZM24eDQYqa/4WKACAgkK53gE=" - - NO_FTP_ACCESS: "yes" - - BOTO_CONFIG: /dev/null - - GO111MODULE: "on" - - GOPROXY: https://proxy.golang.org - -matrix: - allow_failures: - - go: master - env: RUN_LONG_TESTS=no - fast_finish: true - include: - - go: 1.11.x - env: RUN_LONG_TESTS=no - - go: 1.12.x - env: RUN_LONG_TESTS=no - - go: 1.13.x - env: RUN_LONG_TESTS=no - - go: 1.14.x - env: RUN_LONG_TESTS=yes - - go: 1.15.x - env: - - RUN_LONG_TESTS=yes - - DEPLOY_BINARIES=yes - - APTLY_USER=aptly - - secure: "ejVss+Ansvk9f237iXVd87KA8N/SkfJkEdr/KCw9WRkVw3M9WyYtFnqpakIUPFT8RsSc7MW+RU4OM90DsbE9dbDIL0oW+t6QH/IfGjNG2HjDiGEWN/tMLeAQTtzPaVqlItJBo0ILMF2K6NrgkYBYU+tZ8gk5w7CuARvAk82d00o=" - - go: master - env: RUN_LONG_TESTS=no - -before_install: - - virtualenv system/env - - . system/env/bin/activate - - pip install six packaging appdirs - - pip install -U pip setuptools - - pip install -r system/requirements.txt - - make version - -install: - - make prepare - -after_success: - - bash <(curl -s https://codecov.io/bash) - -notifications: - webhooks: - urls: - - "https://webhooks.gitter.im/e/c691da114a41eed6ec45" - on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: false # default: false - -before_deploy: - - make release - -deploy: - - provider: releases - api_key: - secure: XHxYAFBzzgOZyK6JXQpEp0kGrZPmd02esEJjwJXZpWT68kRzCCrBXg+x3vIcgRtl82oQbflv/ThNlGT80iqSmd+Itsa5lUJoJnRxbP8qSykfCXmkrgsHIxbGxWIL+JHAWmwQdkV91kDS04nmjl9MDptLId0tuleWwcMH6h1hgMg= - file_glob: true - file: build/* - skip_cleanup: true - on: - tags: true - condition: "$DEPLOY_BINARIES = yes" - - provider: s3 - access_key_id: - secure: "I2etn22HHsQjJNhr6zdM/P4VLCYwEA/6HEf2eGvwey93oLeog+KnDCUI7lwGAHYuwzyDGQbZZ6YdoNc3b0kxaRWT0W+ke78TAdJhTZ+xbqGfEWv1er0zklJLOsimYF097rDJw8g3Oh/Gjwt5TTp0GJ5l3IhJ6zepNsKCMuwQpJM=" - secret_access_key: - secure: "inRWX7FuyhkhKzGknSd2/mjZaNFZm/zHMejM99OF6PiGLNtyt/esdA0ToYL8B8Icl0/SISlLlEr/DDa4OGENKueFVeHrKH7OK0jVbWp9Yvw4hCXSlw9VmlkHDMQrC4gybS2Hf7el8N4AFVqyeUE7LqiP3WruHRdbE9XgOnTkLkg=" - bucket: aptly-nightly - skip_cleanup: true - acl: public-read - local_dir: build - on: - branch: master - condition: "$DEPLOY_BINARIES = yes" - - provider: script - script: bash upload-artifacts.sh nightly - skip_cleanup: true - on: - branch: master - condition: "$DEPLOY_BINARIES = yes" - - provider: script - script: bash upload-artifacts.sh release - skip_cleanup: true - on: - tags: true - condition: "$DEPLOY_BINARIES = yes" diff --git a/AUTHORS b/AUTHORS index 00bc4557..d62f1573 100644 --- a/AUTHORS +++ b/AUTHORS @@ -49,3 +49,22 @@ List of contributors, in chronological order: * Samuel Mutel (https://github.com/smutel) * Russell Greene (https://github.com/russelltg) * Wade Simmons (https://github.com/wadey) +* Steven Stone (https://github.com/smstone) +* Josh Bayfield (https://github.com/jbayfield) +* Boxjan (https://github.com/boxjan) +* Mauro Regli (https://github.com/reglim) +* Alexander Zubarev (https://github.com/strike) +* Nicolas Dostert (https://github.com/acdn-ndostert) +* Ryan Gonzalez (https://github.com/refi64) +* Paul Cacheux (https://github.com/paulcacheux) +* Nic Waller (https://github.com/sf-nwaller) +* iofq (https://github.com/iofq) +* Noa Resare (https://github.com/nresare) +* Ramon N.Rodriguez (https://github.com/runitonmetal) +* Golf Hu (https://github.com/hudeng-go) +* Cookie Fei (https://github.com/wuhuang26) +* Andrey Loukhnov (https://github.com/aol-nnov) +* Christoph Fiehe (https://github.com/cfiehe) +* Blake Kostner (https://github.com/btkostner) +* Leigh London (https://github.com/leighlondon) +* Gordian Schoenherr (https://github.com/schoenherrg) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ab3791a8..297e9220 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [team@aptly.info](mailto:team@aptly.info). All +reported by contacting the project team on [Aptly Discussions](https://github.com/aptly-dev/aptly/discussions). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a54863d..762cf8a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: -The following is a set of guidelines for contributing to [aptly](https://github.com/smira/aplty) and related repositories, which are hosted in the [aptly-dev Organization](https://github.com/aptly-dev) on GitHub. +The following is a set of guidelines for contributing to [aptly](https://github.com/aptly-dev/aplty) and related repositories, which are hosted in the [aptly-dev Organization](https://github.com/aptly-dev) on GitHub. These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. ## What should I know before I get started? @@ -11,7 +11,7 @@ These are just guidelines, not rules. Use your best judgment, and feel free to p This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. -Please report unacceptable behavior to [team@aptly.info](mailto:team@aptly.info). +Please report unacceptable behavior on [https://github.com/aptly-dev/aptly/discussions](https://github.com/aptly-dev/aptly/discussions) ### List of Repositories @@ -60,7 +60,7 @@ If you want to update website, please follow steps below: We're always looking for new contributions to [FAQ](https://www.aptly.info/doc/faq/), [tutorials](https://www.aptly.info/tutorial/), general fixes, clarifications, misspellings, grammar mistakes! -### Your First Code Contribution +### Code Contribution Please follow [next section](#development-setup) on development process. When change is ready, please submit PR following [PR template](.github/PULL_REQUEST_TEMPLATE.md). @@ -68,63 +68,117 @@ following [PR template](.github/PULL_REQUEST_TEMPLATE.md). Make sure that purpose of your change is clear, all the tests and checks pass, and all new code is covered with tests if that is possible. +### Get the Source + +To clone the git repo, run the following commands: +``` +git clone git@github.com:aptly-dev/aptly.git +cd aptly +``` + ## Development Setup -This section describes local setup to start contributing to aptly source. +Working on aptly code can be done locally on the development machine, or for convenience by using docker. The next sections describe the setup process. -### Go & Python +### Docker Development Setup -You would need `Go` (latest version is recommended) and `Python` 2.7.x (3.x is not supported yet). +This section describes the docker setup to start contributing to aptly. -If you're new to Go, follow [getting started guide](https://golang.org/doc/install) to install it and perform -initial setup. With Go 1.8+, default `$GOPATH` is `$HOME/go`, so rest of this document assumes that. +#### Dependencies -Usually `$GOPATH/bin` is appended to your `$PATH` to make it easier to run built binaries, but you might choose -to prepend it or to skip this test if you're security conscious. +Install the following on your development machine: +- docker +- make +- git -### Forking and Cloning +##### Docker installation on macOS +1. Install [Docker Desktop on Mac](https://docs.docker.com/desktop/setup/install/mac-install/) (or via [Homebrew](https://brew.sh/)) +2. Allow directory sharing + - Open Docker Desktop + - Go to `Settings → Resources → File Sharing → Virtual File Shares` + - Add the aptly git repository path to the shared list (eg. /home/Users/john/aptly) -As aptly is using Go modules, aptly repository could be cloned to any location on the file system: +#### Create docker container - git clone git@github.com:aptly-dev/aptly.git - cd aptly +To build the development docker image, run: +``` +make docker-image +``` -For main repo under your GitHub user and add it as another Git remote: +#### Build aptly - git remote add git@github.com:/aptly.git +To build the aptly in the development docker container, run: +``` +make docker-build +``` -That way you can continue to build project as is (you don't need to adjust import paths), but you would need -to specify your remote name when pushing branches: +#### Running aptly commands - git push +To run aptly commands in the development docker container, run: +``` +make docker-shell +``` -### Dependencies +Example: +``` +$ make docker-shell +aptly@b43e8473ef81:/work/src$ aptly version +aptly version: 1.5.0+189+g0fc90dff +``` -You would need some additional tools and Python virtual environment to run tests and checks, install them with: +#### Running unit tests - make prepare dev system/env +In order to run aptly unit tests, enter the following: +``` +make docker-unit-tests +``` -This is usually one-time action. +#### Running system tests -Aptly is using Go modules to manage dependencies, download modules using: +In order to run aptly system tests, enter the following: +``` +make docker-system-tests +``` - make modules +#### Running golangci-lint -### Building +In order to run aptly unit tests, run: +``` +make docker-lint +``` -If you want to build aptly binary from your current source tree, run: +#### More info + +Run `make help` for more information. + + +### Local Development Setup + +This section describes local setup to start contributing to aptly. + +#### Dependencies + +Building aptly requires go version 1.22. + +On Debian bookworm with backports enabled, go can be installed with: + + apt install -t bookworm-backports golang-go + +#### Building + +To build aptly, run: + + make build + +Run aptly: + + build/aptly + +To install aptly into `$GOPATH/bin`, run: make install -This would build `aptly` in `$GOPATH/bin`, so depending on your `$PATH`, you should be able to run it immediately with: - - aptly - -Or, if it's not on your path: - - ~/go/bin/aptly - -### Unit-tests +#### Unit-tests aptly has two kinds of tests: unit-tests and functional (system) tests. Functional tests are preferred way to test any feature, but some features are much easier to test with unit-tests (e.g. algorithms, failure scenarios, ...) @@ -133,7 +187,7 @@ aptly is using standard Go unit-test infrastructure plus [gocheck](http://labix. make test -### Functional Tests +#### Functional Tests Functional tests are implemented in Python, and they use custom test runner which is similar to Python unit-test runner. Most of the tests start with clean aptly state, run some aptly commands to prepare environment, and finally @@ -180,26 +234,6 @@ There are some packages available under `system/files/` directory which are used this default location. You can run aptly under different user or by using non-default config location with non-default aptly root directory. -### Style Checks - -Style checks could be run with: - - make check - -aptly is using [golangci-lint](https://github.com/golangci/golangci-lint) to run style checks on Go code. Configuration -for the linter could be found in [.golangci.yml](.golangci.yml) file. - -Python code (system tests) are linted with [flake8 tool](https://pypi.python.org/pypi/flake8). - -### Vendored Code - -aptly is using Go vendoring for all the libraries aptly depends upon. `vendor/` directory is checked into the source -repository to avoid any problems if source repositories go away. Go build process will automatically prefer vendored -packages over packages in `$GOPATH`. - -If you want to update vendored dependencies or to introduce new dependency, use [dep tool](https://github.com/golang/dep). -Usually all you need is `dep ensure` or `dep ensure -update`. - ### man Page aptly is using combination of [Go templates](http://godoc.org/text/template) and automatically generated text to build `aptly.1` man page. If either source @@ -217,25 +251,3 @@ Bash and Zsh completion for aptly reside in the same repo under in [completion.d When new option or command is introduced, bash completion should be updated to reflect that change. When aptly package is being built, it automatically pulls bash completion and man page into the package. - -## Design - -This section requires future work. - -*TBD* - -### Database - -### Package Pool - -### Package - -### PackageList, PackageRefList - -### LocalRepo, RemoteRepo, Snapshot - -### PublishedRepository - -### Context - -### Collections, CollectionFactory diff --git a/Makefile b/Makefile index 1acb5035..d6a81da4 100644 --- a/Makefile +++ b/Makefile @@ -1,81 +1,219 @@ -GOVERSION=$(shell go version | awk '{print $$3;}') -ifdef TRAVIS_TAG - TAG=$(TRAVIS_TAG) -else - TAG="$(shell git describe --tags --always)" -endif -VERSION=$(shell echo $(TAG) | sed 's@^v@@' | sed 's@-@+@g') -PACKAGES=context database deb files gpg http query swift s3 utils +GOPATH=$(shell go env GOPATH) +VERSION=$(shell make -s version) PYTHON?=python3 -TESTS?= BINPATH?=$(GOPATH)/bin -RUN_LONG_TESTS?=yes +GOLANGCI_LINT_VERSION=v1.54.1 # version supporting go 1.19 +COVERAGE_DIR?=$(shell mktemp -d) +GOOS=$(shell go env GOHOSTOS) +GOARCH=$(shell go env GOHOSTARCH) -all: modules test bench check system-test +# Uncomment to update system test gold files +# CAPTURE := "--capture" -prepare: - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.43.0 +help: ## Print this help + @grep -E '^[a-zA-Z][a-zA-Z0-9_-]*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -modules: - go mod download +prepare: ## Install go module dependencies + # Prepare go modules go mod verify go mod tidy -v + # Generate VERSION file + go generate -dev: - go get -u github.com/laher/goxc +releasetype: # Print release type: ci (on any branch/commit), release (on a tag) + @reltype=ci ; \ + gitbranch=`git rev-parse --abbrev-ref HEAD` ; \ + if [ "$$gitbranch" = "HEAD" ] && [ "$$FORCE_CI" != "true" ]; then \ + gittag=`git describe --tags --exact-match 2>/dev/null` ;\ + if echo "$$gittag" | grep -q '^v[0-9]'; then \ + reltype=release ; \ + fi ; \ + fi ; \ + echo $$reltype -check: system/env -ifeq ($(RUN_LONG_TESTS), yes) - golangci-lint run - system/env/bin/flake8 -endif +version: ## Print aptly version + @ci="" ; \ + if [ "`make -s releasetype`" = "ci" ]; then \ + ci=`TZ=UTC git show -s --format='+%cd.%h' --date=format-local:'%Y%m%d%H%M%S'`; \ + fi ; \ + if which dpkg-parsechangelog > /dev/null 2>&1; then \ + echo `dpkg-parsechangelog -S Version`$$ci; \ + else \ + echo `grep ^aptly -m1 debian/changelog | sed 's/.*(\([^)]\+\)).*/\1/'`$$ci ; \ + fi + +swagger-install: + # Install swag + @test -f $(BINPATH)/swag || GOOS= GOARCH= go install github.com/swaggo/swag/cmd/swag@latest + # Generate swagger.conf + cp docs/swagger.conf.tpl docs/swagger.conf + echo "// @version $(VERSION)" >> docs/swagger.conf + +azurite-start: + azurite -l /tmp/aptly-azurite & \ + echo $$! > ~/.azurite.pid + +azurite-stop: + @kill `cat ~/.azurite.pid` + +swagger: swagger-install + # Generate swagger docs + @PATH=$(BINPATH)/:$(PATH) swag init --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf + +etcd-install: + # Install etcd + test -d /tmp/aptly-etcd || system/t13_etcd/install-etcd.sh + +flake8: ## run flake8 on system test python files + flake8 system/ + +lint: prepare + # Install golangci-lint + @test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) + # Running lint + @PATH=$(BINPATH)/:$(PATH) golangci-lint run + + +build: prepare swagger ## Build aptly + go build -o build/aptly install: - go install -v -ldflags "-X main.Version=$(VERSION)" + @echo "\e[33m\e[1mBuilding aptly ...\e[0m" + # go generate + @go generate + # go install -v + @out=`mktemp`; if ! go install -v > $$out 2>&1; then cat $$out; rm -f $$out; echo "\nBuild failed\n"; exit 1; else rm -f $$out; fi -system/env: system/requirements.txt -ifeq ($(RUN_LONG_TESTS), yes) - rm -rf system/env - $(PYTHON) -m venv system/env - system/env/bin/pip install -r system/requirements.txt -endif +test: prepare swagger etcd-install ## Run unit tests + @echo "\e[33m\e[1mStarting etcd ...\e[0m" + @mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 & + @echo "\e[33m\e[1mRunning go test ...\e[0m" + go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret + @echo "\e[33m\e[1mStopping etcd ...\e[0m" + @pid=`cat /tmp/etcd.pid`; kill $$pid + @rm -f /tmp/aptly-etcd-data/etcd.log + @ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret -system-test: install system/env -ifeq ($(RUN_LONG_TESTS), yes) +system-test: prepare swagger etcd-install ## Run system tests + # build coverage binary + go test -v -coverpkg="./..." -c -tags testruncli + # Download fixture-db, fixture-pool, etcd.db if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi - PATH=$(BINPATH)/:$(PATH) && . system/env/bin/activate && APTLY_VERSION=$(VERSION) $(PYTHON) system/run.py --long $(TESTS) -endif - -test: - go test -v ./... -gocheck.v=true -race -coverprofile=coverage.txt -covermode=atomic + test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz) + # Run system tests + PATH=$(BINPATH)/:$(PATH) && FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE) $(TEST) bench: + @echo "\e[33m\e[1mRunning benchmark ...\e[0m" go test -v ./deb -run=nothing -bench=. -benchmem +serve: prepare swagger-install ## Run development server (auto recompiling) + test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3 + cp debian/aptly.conf ~/.aptly.conf + sed -i /enable_swagger_endpoint/s/false/true/ ~/.aptly.conf + PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs --generalInfo docs/swagger.conf' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142 + +dpkg: prepare swagger ## Build debian packages + @test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1) + # set debian version + @if [ "`make -s releasetype`" = "ci" ]; then \ + echo CI Build, setting version... ; \ + test ! -f debian/changelog.dpkg-bak || mv debian/changelog.dpkg-bak debian/changelog ; \ + cp debian/changelog debian/changelog.dpkg-bak ; \ + DEBEMAIL="CI " dch -v `make -s version` "CI build" ; \ + fi + # clean + rm -rf obj-i686-linux-gnu obj-arm-linux-gnueabihf obj-aarch64-linux-gnu obj-x86_64-linux-gnu + # Run dpkg-buildpackage + @buildtype="any" ; \ + if [ "$(DEBARCH)" = "amd64" ]; then \ + buildtype="any,all" ; \ + fi ; \ + echo "\e[33m\e[1mBuilding: $$buildtype\e[0m" ; \ + cmd="dpkg-buildpackage -us -uc --build=$$buildtype -d --host-arch=$(DEBARCH)" ; \ + echo "$$cmd" ; \ + $$cmd + lintian ../*_$(DEBARCH).changes || true + # cleanup + @test ! -f debian/changelog.dpkg-bak || mv debian/changelog.dpkg-bak debian/changelog; \ + mkdir -p build && mv ../*.deb build/ ; \ + cd build && ls -l *.deb + +binaries: prepare swagger ## Build binary releases (FreeBSD, MacOS, Linux tar) + # build aptly + GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o build/tmp/aptly -ldflags='-extldflags=-static' + # install + @mkdir -p build/tmp/man build/tmp/completion/bash_completion.d build/tmp/completion/zsh/vendor-completions + @cp man/aptly.1 build/tmp/man/ + @cp completion.d/aptly build/tmp/completion/bash_completion.d/ + @cp completion.d/_aptly build/tmp/completion/zsh/vendor-completions/ + @cp README.rst LICENSE AUTHORS build/tmp/ + @gzip -f build/tmp/man/aptly.1 + @path="aptly_$(VERSION)_$(GOOS)_$(GOARCH)"; \ + rm -rf "build/$$path"; \ + mv build/tmp build/"$$path"; \ + rm -rf build/tmp; \ + cd build; \ + zip -r "$$path".zip "$$path" > /dev/null \ + && echo "Built build/$${path}.zip"; \ + rm -rf "$$path" + +docker-image: ## Build aptly-dev docker image + @docker build -f system/Dockerfile . -t aptly-dev + +docker-build: ## Build aptly in docker container + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper build + +docker-shell: ## Run aptly and other commands in docker container + @docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper || true + +docker-deb: ## Build debian packages in docker container + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64 + +docker-unit-test: ## Run unit tests in docker container + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \ + azurite-start \ + AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \ + AZURE_STORAGE_ACCOUNT=devstoreaccount1 \ + AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \ + test \ + azurite-stop + +docker-system-test: ## Run system tests in docker container (add TEST=t04_mirror or TEST=UpdateMirror26Test to run only specific tests) + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \ + azurite-start \ + AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \ + AZURE_STORAGE_ACCOUNT=devstoreaccount1 \ + AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \ + system-test TEST=$(TEST) \ + azurite-stop + +docker-serve: ## Run development server (auto recompiling) on http://localhost:3142 + @docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper serve || true + +docker-lint: ## Run golangci-lint in docker container + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper lint + +docker-binaries: ## Build binary releases (FreeBSD, MacOS, Linux tar) in docker container + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper binaries + +docker-man: ## Create man page in docker container + @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper man + mem.png: mem.dat mem.gp gnuplot mem.gp open mem.png -goxc: dev - rm -rf root/ - mkdir -p root/usr/share/man/man1/ root/etc/bash_completion.d/ root/usr/share/zsh/vendor-completions/ - cp man/aptly.1 root/usr/share/man/man1 - cp completion.d/aptly root/etc/bash_completion.d/ - cp completion.d/_aptly root/usr/share/zsh/vendor-completions/ - gzip root/usr/share/man/man1/aptly.1 - goxc -pv=$(VERSION) -max-processors=2 $(GOXC_OPTS) - -release: GOXC_OPTS=-tasks-=bintray,go-vet,go-test,rmbin -release: goxc - rm -rf build/ - mkdir -p build/ - mv xc-out/$(VERSION)/aptly_$(VERSION)_* build/ - -man: +man: ## Create man pages make -C man -version: - @echo $(VERSION) +clean: ## remove local build and module cache + # Clean all generated and build files + test ! -e .go || find .go/ -type d ! -perm -u=w -exec chmod u+w {} \; + rm -rf .go/ + rm -rf build/ obj-*-linux-gnu* tmp/ + rm -f unit.out aptly.test VERSION docs/docs.go docs/swagger.json docs/swagger.yaml docs/swagger.conf + find system/ -type d -name __pycache__ -exec rm -rf {} \; 2>/dev/null || true -.PHONY: man modules version release goxc +.PHONY: help man prepare swagger version binaries build docker-release docker-system-test docker-unit-test docker-lint docker-build docker-image docker-man docker-shell docker-serve clean releasetype dpkg serve flake8 diff --git a/README.rst b/README.rst index 13e5b3f1..a53990db 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,7 @@ aptly :target: https://codecov.io/gh/aptly-dev/aptly .. image:: https://badges.gitter.im/Join Chat.svg - :target: https://gitter.im/aptly-dev/aptly?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + :target: https://matrix.to/#/#aptly:gitter.im .. image:: https://goreportcard.com/badge/github.com/aptly-dev/aptly :target: https://goreportcard.com/report/aptly-dev/aptly @@ -39,8 +39,8 @@ Current limitations: * translations are not supported yet -Download --------- +Install Stable Version +----------------------- To install aptly on Debian/Ubuntu, add new repository to ``/etc/apt/sources.list``:: @@ -58,19 +58,28 @@ After that you can install aptly as any other software package:: Don't worry about squeeze part in repo name: aptly package should work on Debian squeeze+, Ubuntu 10.0+. Package contains aptly binary, man page and bash completion. -If you would like to use nightly builds (unstable), please use following repository:: - - deb http://repo.aptly.info/ nightly main +Other Binaries +~~~~~~~~~~~~~~~~~ Binary executables (depends almost only on libc) are available for download from `GitHub Releases `_. -If you have Go environment set up, you can build aptly from source by running (go 1.14+ required):: +Install CI Version +-------------------- - git clone https://github.com/aptly-dev/aptly - cd aptly - make modules install +More recent versions are available as CI builds (development, might be unstable). -Binary would be installed to ``$GOPATH/bin/aptly``. +Debian GNU/Linux +~~~~~~~~~~~~~~~~~ + +Install the following APT key:: + + sudo wget -O /etc/apt/keyrings/aptly.asc https://www.aptly.info/pubkey.txt + +Define CI APT sources in ``/etc/apt/sources.list.d/aptly-ci.list``:: + + deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/ci DIST main + +Where DIST is one of: ``buster``, ``bullseye``, ``bookworm``, ``focal``, ``jammy``, ``noble`` Contributing ------------ diff --git a/api/api.go b/api/api.go index b8656178..2e11c022 100644 --- a/api/api.go +++ b/api/api.go @@ -3,17 +3,18 @@ package api import ( "fmt" - "log" "net/http" "sort" "strconv" "strings" + "sync/atomic" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/query" "github.com/aptly-dev/aptly/task" "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" ) // Lock order acquisition (canonical): @@ -22,11 +23,67 @@ import ( // 3. SnapshotCollection // 4. PublishedRepoCollection -// GET /api/version +type aptlyVersion struct { + // Aptly Version + Version string `json:"Version"` +} + +// @Summary Aptly Version +// @Description **Get aptly version** +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl http://localhost:8080/api/version +// @Description {"Version":"0.9~dev"} +// @Description ``` +// @Tags Status +// @Produce json +// @Success 200 {object} aptlyVersion +// @Router /api/version [get] func apiVersion(c *gin.Context) { c.JSON(200, gin.H{"Version": aptly.Version}) } +type aptlyStatus struct { + // Aptly Status + Status string `json:"Status" example:"'Aptly is ready', 'Aptly is unavailable', 'Aptly is healthy'"` +} + +// @Summary Get Ready State +// @Description **Get aptly ready state** +// @Description +// @Description Return aptly ready state: +// @Description - `Aptly is ready` (HTTP 200) +// @Description - `Aptly is unavailable` (HTTP 503) +// @Tags Status +// @Produce json +// @Success 200 {object} aptlyStatus "Aptly is ready" +// @Failure 503 {object} aptlyStatus "Aptly is unavailable" +// @Router /api/ready [get] +func apiReady(isReady *atomic.Value) func(*gin.Context) { + return func(c *gin.Context) { + if isReady == nil || !isReady.Load().(bool) { + c.JSON(503, gin.H{"Status": "Aptly is unavailable"}) + return + } + + c.JSON(200, gin.H{"Status": "Aptly is ready"}) + } +} + +// @Summary Get Health State +// @Description **Get aptly health state** +// @Description +// @Description Return aptly health state: +// @Description - `Aptly is healthy` (HTTP 200) +// @Tags Status +// @Produce json +// @Success 200 {object} aptlyStatus +// @Router /api/healthy [get] +func apiHealthy(c *gin.Context) { + c.JSON(200, gin.H{"Status": "Aptly is healthy"}) +} + type dbRequestKind int const ( @@ -137,20 +194,29 @@ func maybeRunTaskInBackground(c *gin.Context, name string, resources []string, p // Run this task in background if configured globally or per-request background := truthy(c.DefaultQuery("_async", strconv.FormatBool(context.Config().AsyncAPI))) if background { - log.Println("Executing task asynchronously") + log.Debug().Msg("Executing task asynchronously") task, conflictErr := runTaskInBackground(name, resources, proc) if conflictErr != nil { - c.AbortWithError(409, conflictErr) + AbortWithJSONError(c, 409, conflictErr) return } c.JSON(202, task) } else { - log.Println("Executing task synchronously") - out := context.Progress() - detail := task.Detail{} - retValue, err := proc(out, &detail) + log.Debug().Msg("Executing task synchronously") + task, conflictErr := runTaskInBackground(name, resources, proc) + if conflictErr != nil { + AbortWithJSONError(c, 409, conflictErr) + return + } + + // wait for task to finish + context.TaskList().WaitForTaskByID(task.ID) + + retValue, _ := context.TaskList().GetTaskReturnValueByID(task.ID) + err, _ := context.TaskList().GetTaskErrorByID(task.ID) + context.TaskList().DeleteTaskByID(task.ID) if err != nil { - c.AbortWithError(retValue.Code, err) + AbortWithJSONError(c, retValue.Code, err) return } if retValue != nil { @@ -168,7 +234,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } @@ -176,7 +242,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory if queryS != "" { q, err := query.Parse(c.Request.URL.Query().Get("q")) if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } @@ -193,21 +259,57 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory sort.Strings(architecturesList) if len(architecturesList) == 0 { - c.AbortWithError(400, fmt.Errorf("unable to determine list of architectures, please specify explicitly")) + AbortWithJSONError(c, 400, fmt.Errorf("unable to determine list of architectures, please specify explicitly")) return } } list.PrepareIndex() - list, err = list.Filter([]deb.PackageQuery{q}, withDeps, - nil, context.DependencyOptions(), architecturesList) + list, err = list.Filter(deb.FilterOptions{ + Queries: []deb.PackageQuery{q}, + WithDependencies: withDeps, + Source: nil, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + }) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to search: %s", err)) + AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err)) return } } + // filter packages by version + if c.Request.URL.Query().Get("maximumVersion") == "1" { + 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" { list.ForEach(func(p *deb.Package) error { result = append(result, p) @@ -219,3 +321,8 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory c.JSON(200, list.Strings()) } } + +func AbortWithJSONError(c *gin.Context, code int, err error) *gin.Error { + c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8") + return c.AbortWithError(code, err) +} diff --git a/api/api_test.go b/api/api_test.go index 2e55638e..8c62b31d 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -1,16 +1,20 @@ package api import ( + "bytes" "encoding/json" - ctx "github.com/aptly-dev/aptly/context" - "github.com/gin-gonic/gin" + "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "os" + "strings" "testing" + "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" @@ -30,12 +34,13 @@ type ApiSuite struct { var _ = Suite(&ApiSuite{}) func createTestConfig() *os.File { - file, err := ioutil.TempFile("", "aptly") + file, err := os.CreateTemp("", "aptly") if err != nil { return nil } jsonString, err := json.Marshal(gin.H{ - "architectures": []string{}, + "architectures": []string{}, + "enableMetricsEndpoint": true, }) if err != nil { return nil @@ -44,9 +49,12 @@ func createTestConfig() *os.File { return file } -func (s *ApiSuite) SetUpSuite(c *C) { +func (s *ApiSuite) setupContext() error { + aptly.Version = "testVersion" file := createTestConfig() - c.Assert(file, NotNil) + if nil == file { + return fmt.Errorf("unable to create the test configuration file") + } s.configFile = file flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError) @@ -57,10 +65,19 @@ func (s *ApiSuite) SetUpSuite(c *C) { s.flags = flags context, err := ctx.NewContext(s.flags) - c.Assert(err, IsNil) + if nil != err { + return err + } s.context = context s.router = Router(context) + + return nil +} + +func (s *ApiSuite) SetUpSuite(c *C) { + err := s.setupContext() + c.Assert(err, IsNil) } func (s *ApiSuite) TearDownSuite(c *C) { @@ -85,11 +102,52 @@ func (s *ApiSuite) HTTPRequest(method string, url string, body io.Reader) (*http return w, nil } +func (s *ApiSuite) TestGinRunsInReleaseMode(c *C) { + c.Check(gin.Mode(), Equals, gin.ReleaseMode) +} + func (s *ApiSuite) TestGetVersion(c *C) { response, err := s.HTTPRequest("GET", "/api/version", nil) c.Assert(err, IsNil) c.Check(response.Code, Equals, 200) - c.Check(response.Body.String(), Matches, ".*Version.*") + c.Check(response.Body.String(), Matches, "{\"Version\":\""+aptly.Version+"\"}") +} + +func (s *ApiSuite) TestGetReadiness(c *C) { + response, err := s.HTTPRequest("GET", "/api/ready", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) + c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is ready\"}") +} + +func (s *ApiSuite) TestGetHealthiness(c *C) { + response, err := s.HTTPRequest("GET", "/api/healthy", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) + c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is healthy\"}") +} + +func (s *ApiSuite) TestGetMetrics(c *C) { + response, err := s.HTTPRequest("GET", "/api/metrics", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) + b := strings.Replace(response.Body.String(), "\n", "", -1) + c.Check(b, Matches, ".*# TYPE aptly_api_http_requests_in_flight gauge.*") + c.Check(b, Matches, ".*# TYPE aptly_api_http_requests_total counter.*") + c.Check(b, Matches, ".*# TYPE aptly_api_http_request_size_bytes summary.*") + c.Check(b, Matches, ".*# TYPE aptly_api_http_response_size_bytes summary.*") + c.Check(b, Matches, ".*# TYPE aptly_api_http_request_duration_seconds summary.*") + c.Check(b, Matches, ".*# TYPE aptly_build_info gauge.*") + c.Check(b, Matches, ".*aptly_build_info.*version=\"testVersion\".*") +} + +func (s *ApiSuite) TestRepoCreate(c *C) { + body, err := json.Marshal(gin.H{ + "Name": "dummy", + }) + c.Assert(err, IsNil) + _, err = s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body)) + c.Assert(err, IsNil) } func (s *ApiSuite) TestTruthy(c *C) { diff --git a/api/db.go b/api/db.go index 3f8b826d..259a94aa 100644 --- a/api/db.go +++ b/api/db.go @@ -11,9 +11,17 @@ import ( "github.com/gin-gonic/gin" ) -// POST /api/db/cleanup +// @Summary DB Cleanup +// @Description **Cleanup Aptly DB** +// @Description Database cleanup removes information about unreferenced packages and deletes files in the package pool that aren’t used by packages anymore. +// @Description It is a good idea to run this command after massive deletion of mirrors, snapshots or local repos. +// @Tags Database +// @Produce json +// @Param _async query bool false "Run in background and return task object" +// @Success 200 {object} string "Output" +// @Failure 404 {object} Error "Not Found" +// @Router /api/db/cleanup [post] func apiDbCleanup(c *gin.Context) { - resources := []string{string(task.AllResourcesKey)} maybeRunTaskInBackground(c, "Clean up db", resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { var err error diff --git a/api/error.go b/api/error.go new file mode 100644 index 00000000..d966880d --- /dev/null +++ b/api/error.go @@ -0,0 +1,5 @@ +package api + +type Error struct { + Error string `json:"error"` +} diff --git a/api/files.go b/api/files.go index 41b59b2d..2d042a5d 100644 --- a/api/files.go +++ b/api/files.go @@ -6,8 +6,11 @@ import ( "os" "path/filepath" "strings" + "sync" + "github.com/aptly-dev/aptly/utils" "github.com/gin-gonic/gin" + "github.com/saracen/walker" ) func verifyPath(path string) bool { @@ -24,27 +27,37 @@ func verifyPath(path string) bool { func verifyDir(c *gin.Context) bool { if !verifyPath(c.Params.ByName("dir")) { - c.AbortWithError(400, fmt.Errorf("wrong dir")) + AbortWithJSONError(c, 400, fmt.Errorf("wrong dir")) return false } return true } -// GET /files +// @Summary List Directories +// @Description **Get list of upload directories** +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl http://localhost:8080/api/files +// @Description ["aptly-0.9"] +// @Description ``` +// @Tags Files +// @Produce json +// @Success 200 {array} string "List of files" +// @Router /api/files [get] func apiFilesListDirs(c *gin.Context) { list := []string{} + listLock := &sync.Mutex{} - err := filepath.Walk(context.UploadPath(), func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - + err := walker.Walk(context.UploadPath(), func(path string, info os.FileInfo) error { if path == context.UploadPath() { return nil } if info.IsDir() { + listLock.Lock() + defer listLock.Unlock() list = append(list, filepath.Base(path)) return filepath.SkipDir } @@ -53,30 +66,50 @@ func apiFilesListDirs(c *gin.Context) { }) if err != nil && !os.IsNotExist(err) { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } c.JSON(200, list) } -// POST /files/:dir/ +// @Summary Upload Files +// @Description **Upload files to a directory** +// @Description +// @Description - one or more files can be uploaded +// @Description - existing uploaded are overwritten +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl -X POST -F file=@aptly_0.9~dev+217+ge5d646c_i386.deb http://localhost:8080/api/files/aptly-0.9 +// @Description ["aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb"] +// @Description ``` +// @Tags Files +// @Accept multipart/form-data +// @Param dir path string true "Directory to upload files to. Created if does not exist" +// @Param files formData file true "Files to upload" +// @Produce json +// @Success 200 {array} string "list of uploaded files" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/files/{dir} [post] func apiFilesUpload(c *gin.Context) { if !verifyDir(c) { return } - path := filepath.Join(context.UploadPath(), c.Params.ByName("dir")) + path := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir"))) err := os.MkdirAll(path, 0777) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } err = c.Request.ParseMultipartForm(10 * 1024 * 1024) if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } @@ -86,7 +119,7 @@ func apiFilesUpload(c *gin.Context) { for _, file := range files { src, err := file.Open() if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } defer src.Close() @@ -94,14 +127,14 @@ func apiFilesUpload(c *gin.Context) { destPath := filepath.Join(path, filepath.Base(file.Filename)) dst, err := os.Create(destPath) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } defer dst.Close() _, err = io.Copy(dst, src) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } @@ -109,20 +142,35 @@ func apiFilesUpload(c *gin.Context) { } } + apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc() c.JSON(200, stored) - } -// GET /files/:dir +// @Summary List Files +// @Description **Show uploaded files in upload directory** +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl http://localhost:8080/api/files/aptly-0.9 +// @Description ["aptly_0.9~dev+217+ge5d646c_i386.deb"] +// @Description ``` +// @Tags Files +// @Produce json +// @Param dir path string true "Directory to list" +// @Success 200 {array} string "Files found in directory" +// @Failure 404 {object} Error "Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/files/{dir} [get] func apiFilesListFiles(c *gin.Context) { if !verifyDir(c) { return } list := []string{} - root := filepath.Join(context.UploadPath(), c.Params.ByName("dir")) + listLock := &sync.Mutex{} + root := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir"))) - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(root, func(path string, _ os.FileInfo, err error) error { if err != nil { return err } @@ -131,6 +179,8 @@ func apiFilesListFiles(c *gin.Context) { return nil } + listLock.Lock() + defer listLock.Unlock() list = append(list, filepath.Base(path)) return nil @@ -138,9 +188,9 @@ func apiFilesListFiles(c *gin.Context) { if err != nil { if os.IsNotExist(err) { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) } else { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) } return } @@ -148,36 +198,66 @@ func apiFilesListFiles(c *gin.Context) { c.JSON(200, list) } -// DELETE /files/:dir +// @Summary Delete Directory +// @Description **Delete upload directory and uploaded files within** +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9 +// @Description {} +// @Description ``` +// @Tags Files +// @Produce json +// @Param dir path string true "Directory" +// @Success 200 {object} string "msg" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/files/{dir} [delete] func apiFilesDeleteDir(c *gin.Context) { if !verifyDir(c) { return } - err := os.RemoveAll(filepath.Join(context.UploadPath(), c.Params.ByName("dir"))) + err := os.RemoveAll(filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir")))) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } c.JSON(200, gin.H{}) } -// DELETE /files/:dir/:name +// @Summary Delete File +// @Description **Delete a uploaded file in upload directory** +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb +// @Description {} +// @Description ``` +// @Tags Files +// @Produce json +// @Param dir path string true "Directory to delete from" +// @Param name path string true "File to delete" +// @Success 200 {object} string "msg" +// @Failure 400 {object} Error "Bad Request" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/files/{dir}/{name} [delete] func apiFilesDeleteFile(c *gin.Context) { if !verifyDir(c) { return } - if !verifyPath(c.Params.ByName("name")) { - c.AbortWithError(400, fmt.Errorf("wrong file")) + dir := utils.SanitizePath(c.Params.ByName("dir")) + name := utils.SanitizePath(c.Params.ByName("name")) + if !verifyPath(name) { + AbortWithJSONError(c, 400, fmt.Errorf("wrong file")) return } - err := os.Remove(filepath.Join(context.UploadPath(), c.Params.ByName("dir"), c.Params.ByName("name"))) + err := os.Remove(filepath.Join(context.UploadPath(), dir, name)) if err != nil { if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } } diff --git a/api/gpg.go b/api/gpg.go index 640e45c7..4ea79b68 100644 --- a/api/gpg.go +++ b/api/gpg.go @@ -2,31 +2,49 @@ package api import ( "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" "strings" "github.com/aptly-dev/aptly/pgp" + "github.com/aptly-dev/aptly/utils" "github.com/gin-gonic/gin" ) -// POST /api/gpg -func apiGPGAddKey(c *gin.Context) { - var b struct { - Keyserver string - GpgKeyID string - GpgKeyArmor string - Keyring string - } +type gpgAddKeyParams struct { + // Keyserver, when downloading GpgKeyIDs + Keyserver string `json:"Keyserver" example:"hkp://keyserver.ubuntu.com:80"` + // GpgKeyIDs to download from Keyserver, comma separated list + GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500,8B48AD6246925553"` + // Armored gpg public ket, instead of downloading from keyserver + GpgKeyArmor string `json:"GpgKeyArmor" example:""` + // Keyring for adding the keys (default: trustedkeys.gpg) + Keyring string `json:"Keyring" example:"trustedkeys.gpg"` +} +// @Summary Add GPG Keys +// @Description **Adds GPG keys to aptly keyring** +// @Description +// @Description Add GPG public keys for veryfing remote repositories for mirroring. +// @Tags Mirrors +// @Produce json +// @Success 200 {object} string "OK" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Router /api/gpg [post] +func apiGPGAddKey(c *gin.Context) { + b := gpgAddKeyParams{} if c.Bind(&b) != nil { return } + b.Keyserver = utils.SanitizePath(b.Keyserver) + b.GpgKeyID = utils.SanitizePath(b.GpgKeyID) + b.GpgKeyArmor = utils.SanitizePath(b.GpgKeyArmor) + // b.Keyring can be an absolute path var err error - args := []string{"--no-default-keyring"} + args := []string{"--no-default-keyring", "--allow-non-selfsigned-uid"} keyring := "trustedkeys.gpg" if len(b.Keyring) > 0 { keyring = b.Keyring @@ -37,9 +55,9 @@ func apiGPGAddKey(c *gin.Context) { } if len(b.GpgKeyArmor) > 0 { var tempdir string - tempdir, err = ioutil.TempDir(os.TempDir(), "aptly") + tempdir, err = os.MkdirTemp(os.TempDir(), "aptly") if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } defer os.RemoveAll(tempdir) @@ -47,11 +65,11 @@ func apiGPGAddKey(c *gin.Context) { keypath := filepath.Join(tempdir, "key") keyfile, e := os.Create(keypath) if e != nil { - c.AbortWithError(400, e) + AbortWithJSONError(c, 400, e) return } if _, e = keyfile.WriteString(b.GpgKeyArmor); e != nil { - c.AbortWithError(400, e) + AbortWithJSONError(c, 400, e) } args = append(args, "--import", keypath) @@ -62,10 +80,10 @@ func apiGPGAddKey(c *gin.Context) { args = append(args, keys...) } - finder := pgp.GPG1Finder() + finder := pgp.GPGDefaultFinder() gpg, _, err := finder.FindGPG() if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } @@ -74,11 +92,11 @@ func apiGPGAddKey(c *gin.Context) { // there is no error handling for such as gpg will do this for us cmd := exec.Command(gpg, args...) fmt.Printf("running %s %s\n", gpg, strings.Join(args, " ")) - cmd.Stdout = os.Stdout - if err = cmd.Run(); err != nil { - c.AbortWithError(400, err) + out, err := cmd.CombinedOutput() + if err != nil { + c.JSON(400, string(out)) return } - c.JSON(200, gin.H{}) + c.JSON(200, string(out)) } diff --git a/api/graph.go b/api/graph.go index c79b861e..78d4958f 100644 --- a/api/graph.go +++ b/api/graph.go @@ -12,7 +12,27 @@ import ( "github.com/gin-gonic/gin" ) -// GET /api/graph.:ext?layout=[vertical|horizontal(default)] +// @Summary Graph Output +// @Description **Generate dependency graph** +// @Description +// @Description Command graph generates graph of dependencies: +// @Description +// @Description * between snapshots and mirrors (what mirror was used to create each snapshot) +// @Description * between snapshots and local repos (what local repo was used to create snapshot) +// @Description * between snapshots (pulling, merging, etc.) +// @Description * between snapshots, local repos and published repositories (how snapshots were published). +// @Description +// @Description Graph is rendered to PNG file using graphviz package. +// @Description +// @Description Example URL: `http://localhost:8080/api/graph.svg?layout=vertical` +// @Tags Status +// @Produce image/png, image/svg+xml +// @Param ext path string true "ext specifies desired file extension, e.g. .png, .svg." +// @Param layout query string false "Change between a `horizontal` (default) and a `vertical` graph layout." +// @Success 200 {object} []byte "Output" +// @Failure 404 {object} Error "Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/graph.{ext} [get] func apiGraph(c *gin.Context) { var ( err error @@ -43,25 +63,25 @@ func apiGraph(c *gin.Context) { stdin, err := command.StdinPipe() if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } _, err = io.Copy(stdin, buf) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } err = stdin.Close() if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } output, err = command.Output() if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err)) + AbortWithJSONError(c, 500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err)) return } diff --git a/api/metrics.go b/api/metrics.go new file mode 100644 index 00000000..94a9dc25 --- /dev/null +++ b/api/metrics.go @@ -0,0 +1,116 @@ +package api + +import ( + "fmt" + "runtime" + + "github.com/aptly-dev/aptly/aptly" + "github.com/aptly-dev/aptly/deb" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/rs/zerolog/log" +) + +var ( + apiRequestsInFlightGauge = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "aptly_api_http_requests_in_flight", + Help: "Number of concurrent HTTP api requests currently handled.", + }, + []string{"method", "path"}, + ) + apiRequestsTotalCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "aptly_api_http_requests_total", + Help: "Total number of api requests.", + }, + []string{"code", "method", "path"}, + ) + apiRequestSizeSummary = promauto.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "aptly_api_http_request_size_bytes", + Help: "Api HTTP request size in bytes.", + }, + []string{"code", "method", "path"}, + ) + apiResponseSizeSummary = promauto.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "aptly_api_http_response_size_bytes", + Help: "Api HTTP response size in bytes.", + }, + []string{"code", "method", "path"}, + ) + apiRequestsDurationSummary = promauto.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "aptly_api_http_request_duration_seconds", + Help: "Duration of api requests in seconds.", + }, + []string{"code", "method", "path"}, + ) + apiVersionGauge = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "aptly_build_info", + Help: "Metric with a constant '1' value labeled by version and goversion from which aptly was built.", + }, + []string{"version", "goversion"}, + ) + apiFilesUploadedCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "aptly_api_files_uploaded_total", + Help: "Total number of uploaded files labeled by upload directory.", + }, + []string{"directory"}, + ) + apiReposPackageCountGauge = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "aptly_repos_package_count", + Help: "Current number of published packages labeled by source, distribution and component.", + }, + []string{"source", "distribution", "component"}, + ) +) + +type metricsCollectorRegistrar struct { + hasRegistered bool +} + +func (r *metricsCollectorRegistrar) Register(router *gin.Engine) { + if !r.hasRegistered { + apiVersionGauge.WithLabelValues(aptly.Version, runtime.Version()).Set(1) + router.Use(instrumentHandlerInFlight(apiRequestsInFlightGauge, getBasePath)) + router.Use(instrumentHandlerCounter(apiRequestsTotalCounter, getBasePath)) + router.Use(instrumentHandlerRequestSize(apiRequestSizeSummary, getBasePath)) + router.Use(instrumentHandlerResponseSize(apiResponseSizeSummary, getBasePath)) + router.Use(instrumentHandlerDuration(apiRequestsDurationSummary, getBasePath)) + r.hasRegistered = true + } +} + +var MetricsCollectorRegistrar = metricsCollectorRegistrar{hasRegistered: false} + +func countPackagesByRepos() { + err := context.NewCollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error { + err := context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.NewCollectionFactory()) + if err != nil { + msg := fmt.Sprintf( + "Error %s found while determining package count for metrics endpoint (prefix:%s / distribution:%s / component:%s\n).", + err, repo.StoragePrefix(), repo.Distribution, repo.Components()) + log.Warn().Msg(msg) + return err + } + + components := repo.Components() + for _, c := range components { + count := float64(len(repo.RefList(c).Refs)) + apiReposPackageCountGauge.WithLabelValues(fmt.Sprintf("%s", (repo.SourceNames())), repo.Distribution, c).Set(count) + } + + return nil + }) + + if err != nil { + msg := fmt.Sprintf("Error %s found while listing published repos for metrics endpoint", err) + log.Warn().Msg(msg) + } +} diff --git a/api/middleware.go b/api/middleware.go index 10294251..8d18eedb 100644 --- a/api/middleware.go +++ b/api/middleware.go @@ -9,59 +9,36 @@ import ( "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) - -var ( - apiRequestsInFlightGauge = promauto.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "aptly_api_http_requests_in_flight", - Help: "Number of concurrent HTTP api requests currently handled.", - }, - []string{"method", "path"}, - ) - apiRequestsTotalCounter = promauto.NewCounterVec( - prometheus.CounterOpts{ - Name: "aptly_api_http_requests_total", - Help: "Total number of api requests.", - }, - []string{"code", "method", "path"}, - ) - apiRequestSizeSummary = promauto.NewSummaryVec( - prometheus.SummaryOpts{ - Name: "aptly_api_http_request_size_bytes", - Help: "Api HTTP request size in bytes.", - }, - []string{"code", "method", "path"}, - ) - apiResponseSizeSummary = promauto.NewSummaryVec( - prometheus.SummaryOpts{ - Name: "aptly_api_http_response_size_bytes", - Help: "Api HTTP response size in bytes.", - }, - []string{"code", "method", "path"}, - ) - apiRequestsDurationSummary = promauto.NewSummaryVec( - prometheus.SummaryOpts{ - Name: "aptly_api_http_request_duration_seconds", - Help: "Duration of api requests in seconds.", - }, - []string{"code", "method", "path"}, - ) + "github.com/rs/zerolog/log" ) // Only use base path as label value (e.g.: /api/repos) because of time series cardinality // See https://prometheus.io/docs/practices/naming/#labels func getBasePath(c *gin.Context) string { - return fmt.Sprintf("%s%s", getURLSegment(c.Request.URL.Path, 0), getURLSegment(c.Request.URL.Path, 1)) + segment0, err := getURLSegment(c.Request.URL.Path, 0) + if err != nil { + return "/" + } + segment1, err := getURLSegment(c.Request.URL.Path, 1) + if err != nil { + return *segment0 + } + + return *segment0 + *segment1 } -func getURLSegment(url string, idx int) string { - var urlSegments = strings.Split(url, "/") - +func getURLSegment(url string, idx int) (*string, error) { + urlSegments := strings.Split(url, "/") // Remove segment at index 0 because it's an empty string - var segmentAtIndex = urlSegments[1:cap(urlSegments)][idx] - return fmt.Sprintf("/%s", segmentAtIndex) + urlSegments = urlSegments[1:cap(urlSegments)] + + if len(urlSegments) <= idx { + return nil, fmt.Errorf("index %d out of range, only has %d url segments", idx, len(urlSegments)) + } + + segmentAtIndex := urlSegments[idx] + s := fmt.Sprintf("/%s", segmentAtIndex) + return &s, nil } func instrumentHandlerInFlight(g *prometheus.GaugeVec, pathFunc func(*gin.Context) string) func(*gin.Context) { @@ -101,3 +78,39 @@ func instrumentHandlerDuration(obs prometheus.ObserverVec, pathFunc func(*gin.Co obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(time.Since(now).Seconds()) } } + +// JSONLogger is a gin middleware that takes an instance of Logger and uses it for writing access +// logs that include error messages if there are any. +func JSONLogger() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + path := c.Request.URL.Path + raw := c.Request.URL.RawQuery + + // Process request + c.Next() + + ts := time.Now() + if raw != "" { + path = path + "?" + raw + } + + errorMessage := strings.TrimSuffix(c.Errors.ByType(gin.ErrorTypePrivate).String(), "\n") + l := log.With().Str("remote", c.ClientIP()).Logger(). + With().Str("method", c.Request.Method).Logger(). + With().Str("path", path).Logger(). + With().Str("protocol", c.Request.Proto).Logger(). + With().Str("code", fmt.Sprint(c.Writer.Status())).Logger(). + With().Str("latency", ts.Sub(start).String()).Logger(). + With().Str("agent", c.Request.UserAgent()).Logger() + + if c.Writer.Status() >= 400 && c.Writer.Status() < 500 { + l.Warn().Msg(errorMessage) + } else if c.Writer.Status() >= 500 { + l.Error().Msg(errorMessage) + } else { + l.Info().Msg(errorMessage) + } + } +} diff --git a/api/middleware_test.go b/api/middleware_test.go new file mode 100644 index 00000000..0681785c --- /dev/null +++ b/api/middleware_test.go @@ -0,0 +1,255 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "sync/atomic" + + "github.com/aptly-dev/aptly/utils" + "github.com/gin-gonic/gin" + . "gopkg.in/check.v1" +) + +type MiddlewareSuite struct { + router http.Handler + context *gin.Context + logReader *os.File + logWriter *os.File +} + +var _ = Suite(&MiddlewareSuite{}) + +func (s *MiddlewareSuite) SetUpTest(c *C) { + r, w, err := os.Pipe() + c.Assert(err, IsNil) + + utils.SetupJSONLogger("debug", w) + mw := JSONLogger() + + router := gin.New() + router.UseRawPath = true + router.Use(mw) + router.Use(gin.Recovery(), gin.ErrorLogger()) + + root := router.Group("/api") + isReady := &atomic.Value{} + isReady.Store(false) + root.GET("/ready", apiReady(isReady)) + root.GET("/healthy", apiHealthy) + + s.router = router + s.logReader = r + s.logWriter = w +} + +func (s *MiddlewareSuite) TearDownTest(c *C) { + s.router = nil + s.context = nil + s.logReader = nil + s.logWriter = nil +} + +func (s *MiddlewareSuite) HTTPRequest(method string, url string, body io.Reader) { + recorder := httptest.NewRecorder() + s.context, _ = gin.CreateTestContext(recorder) + req, _ := http.NewRequestWithContext(s.context, method, url, body) + s.context.Request = req + req.Header.Add("Content-Type", "application/json") + s.router.ServeHTTP(httptest.NewRecorder(), req) +} + +func (s *MiddlewareSuite) TestJSONMiddleware4xx(c *C) { + outC := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, s.logReader) + fmt.Println(buf.String()) + outC <- buf.String() + }() + + s.HTTPRequest(http.MethodGet, "/", nil) + s.logWriter.Close() + capturedOutput := <-outC + + var jsonMap map[string]interface{} + json.Unmarshal([]byte(capturedOutput), &jsonMap) + + if val, ok := jsonMap["level"]; ok { + c.Check(val, Equals, "warn") + } else { + c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput) + } + + if val, ok := jsonMap["method"]; ok { + c.Check(val, Equals, "GET") + } else { + c.Errorf("Log message didn't have a 'method' key, obtained %s", capturedOutput) + } + + if val, ok := jsonMap["path"]; ok { + c.Check(val, Equals, "/") + } else { + c.Errorf("Log message didn't have a 'path' key, obtained %s", capturedOutput) + } + + if val, ok := jsonMap["protocol"]; ok { + c.Check(val, Equals, "HTTP/1.1") + } else { + c.Errorf("Log message didn't have a 'protocol' key, obtained %s", capturedOutput) + } + + if val, ok := jsonMap["code"]; ok { + c.Check(val, Equals, "404") + } else { + c.Errorf("Log message didn't have a 'code' key, obtained %s", capturedOutput) + } + + if _, ok := jsonMap["remote"]; !ok { + c.Errorf("Log message didn't have a 'remote' key, obtained %s", capturedOutput) + } + + if _, ok := jsonMap["latency"]; !ok { + c.Errorf("Log message didn't have a 'latency' key, obtained %s", capturedOutput) + } + + if _, ok := jsonMap["agent"]; !ok { + c.Errorf("Log message didn't have a 'agent' key, obtained %s", capturedOutput) + } + + if _, ok := jsonMap["time"]; !ok { + c.Errorf("Log message didn't have a 'time' key, obtained %s", capturedOutput) + } +} + +func (s *MiddlewareSuite) TestJSONMiddleware2xx(c *C) { + outC := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, s.logReader) + fmt.Println(buf.String()) + outC <- buf.String() + }() + + s.HTTPRequest(http.MethodGet, "/api/healthy", nil) + s.logWriter.Close() + capturedOutput := <-outC + + var jsonMap map[string]interface{} + json.Unmarshal([]byte(capturedOutput), &jsonMap) + + if val, ok := jsonMap["level"]; ok { + c.Check(val, Equals, "info") + } else { + c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput) + } +} + +func (s *MiddlewareSuite) TestJSONMiddleware5xx(c *C) { + outC := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, s.logReader) + fmt.Println(buf.String()) + outC <- buf.String() + }() + + s.HTTPRequest(http.MethodGet, "/api/ready", nil) + s.logWriter.Close() + capturedOutput := <-outC + + var jsonMap map[string]interface{} + json.Unmarshal([]byte(capturedOutput), &jsonMap) + + if val, ok := jsonMap["level"]; ok { + c.Check(val, Equals, "error") + } else { + c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput) + } +} + +func (s *MiddlewareSuite) TestJSONMiddlewareRaw(c *C) { + outC := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, s.logReader) + fmt.Println(buf.String()) + outC <- buf.String() + }() + + s.HTTPRequest(http.MethodGet, "/api/healthy?test=raw", nil) + s.logWriter.Close() + capturedOutput := <-outC + + var jsonMap map[string]interface{} + json.Unmarshal([]byte(capturedOutput), &jsonMap) + + fmt.Println(capturedOutput) + + if val, ok := jsonMap["level"]; ok { + c.Check(val, Equals, "info") + } else { + c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput) + } +} + +func (s *MiddlewareSuite) TestGetBasePath(c *C) { + s.HTTPRequest(http.MethodGet, "", nil) + path := getBasePath(s.context) + c.Check(path, Equals, "/") + + s.HTTPRequest(http.MethodGet, "/", nil) + path = getBasePath(s.context) + c.Check(path, Equals, "/") + + s.HTTPRequest(http.MethodGet, "/api", nil) + path = getBasePath(s.context) + c.Check(path, Equals, "/api") + + s.HTTPRequest(http.MethodGet, "/api/repos/testRepo", nil) + path = getBasePath(s.context) + c.Check(path, Equals, "/api/repos") +} + +func (s *MiddlewareSuite) TestGetURLSegment(c *C) { + url := "/" + segment, err := getURLSegment(url, 0) + if err != nil { + c.Error(err) + } + c.Check(*segment, Equals, "/") + + _, err = getURLSegment(url, 1) + if err == nil { + c.Error("Invalid return value") + } + + url = "/api" + segment, err = getURLSegment(url, 0) + if err != nil { + c.Error(err) + } + c.Check(*segment, Equals, "/api") + + _, err = getURLSegment(url, 1) + if err == nil { + c.Error("Invalid return value") + } + + url = "/api/repos/testRepo" + segment, err = getURLSegment(url, 0) + if err != nil { + c.Error(err) + } + c.Check(*segment, Equals, "/api") + + segment, err = getURLSegment(url, 1) + if err != nil { + c.Error(err) + } + c.Check(*segment, Equals, "/repos") +} diff --git a/api/mirror.go b/api/mirror.go index 7775f871..df6ea643 100644 --- a/api/mirror.go +++ b/api/mirror.go @@ -2,8 +2,8 @@ package api import ( "fmt" - "log" "net/http" + "os" "sort" "strings" "sync" @@ -14,19 +14,16 @@ import ( "github.com/aptly-dev/aptly/query" "github.com/aptly-dev/aptly/task" "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" ) -func getVerifier(ignoreSignatures bool, keyRings []string) (pgp.Verifier, error) { - if ignoreSignatures { - return nil, nil - } - +func getVerifier(keyRings []string) (pgp.Verifier, error) { verifier := context.GetVerifier() for _, keyRing := range keyRings { verifier.AddKeyring(keyRing) } - err := verifier.InitKeyring() + err := verifier.InitKeyring(false) if err != nil { return nil, err } @@ -34,7 +31,13 @@ func getVerifier(ignoreSignatures bool, keyRings []string) (pgp.Verifier, error) return verifier, nil } -// GET /api/mirrors +// @Summary List Mirrors +// @Description **Show list of currently available mirrors** +// @Description Each mirror is returned as in “show” API. +// @Tags Mirrors +// @Produce json +// @Success 200 {array} deb.RemoteRepo +// @Router /api/mirrors [get] func apiMirrorsList(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.RemoteRepoCollection() @@ -48,24 +51,49 @@ func apiMirrorsList(c *gin.Context) { c.JSON(200, result) } -// POST /api/mirrors +type mirrorCreateParams struct { + // Name of mirror to be created + Name string `binding:"required" json:"Name" example:"mirror2"` + // Url of the archive to mirror + ArchiveURL string `binding:"required" json:"ArchiveURL" example:"http://deb.debian.org/debian"` + // Distribution name to mirror + Distribution string ` json:"Distribution" example:"'buster', for flat repositories use './'"` + // Package query that is applied to mirror packages + Filter string ` json:"Filter" example:"xserver-xorg"` + // Components to mirror, if not specified aptly would fetch all components + Components []string ` json:"Components" example:"main"` + // Limit mirror to those architectures, if not specified aptly would fetch all architectures + Architectures []string ` json:"Architectures" example:"amd64"` + // Gpg keyring(s) for verifying Release file + Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"` + // Set "true" to mirror source packages + DownloadSources bool ` json:"DownloadSources"` + // Set "true" to mirror udeb files + DownloadUdebs bool ` json:"DownloadUdebs"` + // Set "true" to mirror installer files + DownloadInstaller bool ` json:"DownloadInstaller"` + // Set "true" to include dependencies of matching packages when filtering + FilterWithDeps bool ` json:"FilterWithDeps"` + // Set "true" to skip if the given components are in the Release file + SkipComponentCheck bool ` json:"SkipComponentCheck"` + // Set "true" to skip the verification of architectures + SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"` + // Set "true" to skip the verification of Release file signatures + IgnoreSignatures bool ` json:"IgnoreSignatures"` +} + +// @Summary Create Mirror +// @Description **Create a mirror of a remote repository** +// @Tags Mirrors +// @Consume json +// @Param request body mirrorCreateParams true "Parameters" +// @Produce json +// @Success 200 {object} deb.RemoteRepo +// @Failure 400 {object} Error "Bad Request" +// @Router /api/mirrors [post] func apiMirrorsCreate(c *gin.Context) { var err error - var b struct { - Name string `binding:"required"` - ArchiveURL string `binding:"required"` - Distribution string - Filter string - Components []string - Architectures []string - Keyrings []string - DownloadSources bool - DownloadUdebs bool - DownloadInstaller bool - FilterWithDeps bool - SkipComponentCheck bool - IgnoreSignatures bool - } + var b mirrorCreateParams b.DownloadSources = context.Config().DownloadSourcePackages b.IgnoreSignatures = context.Config().GpgDisableVerify @@ -81,7 +109,7 @@ func apiMirrorsCreate(c *gin.Context) { if strings.HasPrefix(b.ArchiveURL, "ppa:") { b.ArchiveURL, b.Distribution, b.Components, err = deb.ParsePPA(b.ArchiveURL, context.Config()) if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } } @@ -89,7 +117,7 @@ func apiMirrorsCreate(c *gin.Context) { if b.Filter != "" { _, err = query.Parse(b.Filter) if err != nil { - c.AbortWithError(400, fmt.Errorf("unable to create mirror: %s", err)) + AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err)) return } } @@ -98,39 +126,51 @@ func apiMirrorsCreate(c *gin.Context) { b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller) if err != nil { - c.AbortWithError(400, fmt.Errorf("unable to create mirror: %s", err)) + AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err)) return } repo.Filter = b.Filter repo.FilterWithDeps = b.FilterWithDeps repo.SkipComponentCheck = b.SkipComponentCheck + repo.SkipArchitectureCheck = b.SkipArchitectureCheck repo.DownloadSources = b.DownloadSources repo.DownloadUdebs = b.DownloadUdebs - verifier, err := getVerifier(b.IgnoreSignatures, b.Keyrings) + verifier, err := getVerifier(b.Keyrings) if err != nil { - c.AbortWithError(400, fmt.Errorf("unable to initialize GPG verifier: %s", err)) + AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err)) return } downloader := context.NewDownloader(nil) - err = repo.Fetch(downloader, verifier) + err = repo.Fetch(downloader, verifier, b.IgnoreSignatures) if err != nil { - c.AbortWithError(400, fmt.Errorf("unable to fetch mirror: %s", err)) + AbortWithJSONError(c, 400, fmt.Errorf("unable to fetch mirror: %s", err)) return } err = collection.Add(repo) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to add mirror: %s", err)) + AbortWithJSONError(c, 500, fmt.Errorf("unable to add mirror: %s", err)) return } c.JSON(201, repo) } -// DELETE /api/mirrors/:name +// @Summary Delete Mirror +// @Description **Delete a mirror** +// @Tags Mirrors +// @Param name path string true "mirror name" +// @Param force query int true "force: 1 to enable" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 {object} task.ProcessReturnValue +// @Failure 404 {object} Error "Mirror not found" +// @Failure 403 {object} Error "Unable to delete mirror with snapshots" +// @Failure 500 {object} Error "Unable to delete" +// @Router /api/mirrors/{name} [delete] func apiMirrorsDrop(c *gin.Context) { name := c.Params.ByName("name") force := c.Request.URL.Query().Get("force") == "1" @@ -141,13 +181,13 @@ func apiMirrorsDrop(c *gin.Context) { repo, err := mirrorCollection.ByName(name) if err != nil { - c.AbortWithError(404, fmt.Errorf("unable to drop: %s", err)) + AbortWithJSONError(c, 404, fmt.Errorf("unable to drop: %s", err)) return } resources := []string{string(repo.Key())} taskName := fmt.Sprintf("Delete mirror %s", name) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { err := repo.CheckLock() if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err) @@ -157,7 +197,7 @@ func apiMirrorsDrop(c *gin.Context) { snapshots := snapshotCollection.ByRemoteRepoSource(repo) if len(snapshots) > 0 { - return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override") + return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override") } } @@ -169,7 +209,15 @@ func apiMirrorsDrop(c *gin.Context) { }) } -// GET /api/mirrors/:name +// @Summary Get Mirror Info +// @Description **Get mirror information by name** +// @Tags Mirrors +// @Param name path string true "mirror name" +// @Produce json +// @Success 200 {object} deb.RemoteRepo +// @Failure 404 {object} Error "Mirror not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/mirrors/{name} [get] func apiMirrorsShow(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.RemoteRepoCollection() @@ -177,19 +225,30 @@ func apiMirrorsShow(c *gin.Context) { name := c.Params.ByName("name") repo, err := collection.ByName(name) if err != nil { - c.AbortWithError(404, fmt.Errorf("unable to show: %s", err)) + AbortWithJSONError(c, 404, fmt.Errorf("unable to show: %s", err)) return } err = collection.LoadComplete(repo) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to show: %s", err)) + AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err)) } c.JSON(200, repo) } -// GET /api/mirrors/:name/packages +// @Summary List Mirror Packages +// @Description **Get a list of packages from a mirror** +// @Tags Mirrors +// @Param name path string true "mirror name" +// @Param q query string false "search query" +// @Param format query string false "format: `details` for more detailed information" +// @Produce json +// @Success 200 {array} deb.Package "List of Packages" +// @Failure 400 {object} Error "Unable to determine list of architectures" +// @Failure 404 {object} Error "Mirror not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/mirrors/{name}/packages [get] func apiMirrorsPackages(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.RemoteRepoCollection() @@ -197,17 +256,17 @@ func apiMirrorsPackages(c *gin.Context) { name := c.Params.ByName("name") repo, err := collection.ByName(name) if err != nil { - c.AbortWithError(404, fmt.Errorf("unable to show: %s", err)) + AbortWithJSONError(c, 404, fmt.Errorf("unable to show: %s", err)) return } err = collection.LoadComplete(repo) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to show: %s", err)) + AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err)) } if repo.LastDownloadDate.IsZero() { - c.AbortWithError(404, fmt.Errorf("unable to show package list, mirror hasn't been downloaded yet")) + AbortWithJSONError(c, 404, fmt.Errorf("unable to show package list, mirror hasn't been downloaded yet")) return } @@ -216,7 +275,7 @@ func apiMirrorsPackages(c *gin.Context) { list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } @@ -224,7 +283,7 @@ func apiMirrorsPackages(c *gin.Context) { if queryS != "" { q, err := query.Parse(c.Request.URL.Query().Get("q")) if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } @@ -241,17 +300,21 @@ func apiMirrorsPackages(c *gin.Context) { sort.Strings(architecturesList) if len(architecturesList) == 0 { - c.AbortWithError(400, fmt.Errorf("unable to determine list of architectures, please specify explicitly")) + AbortWithJSONError(c, 400, fmt.Errorf("unable to determine list of architectures, please specify explicitly")) return } } list.PrepareIndex() - list, err = list.Filter([]deb.PackageQuery{q}, withDeps, - nil, context.DependencyOptions(), architecturesList) + list, err = list.Filter(deb.FilterOptions{ + Queries: []deb.PackageQuery{q}, + WithDependencies: withDeps, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + }) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to search: %s", err)) + AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err)) } } @@ -267,36 +330,66 @@ func apiMirrorsPackages(c *gin.Context) { } } -// PUT /api/mirrors/:name +type mirrorUpdateParams struct { + // Change mirror name to `Name` + Name string ` json:"Name" example:"mirror1"` + // Url of the archive to mirror + ArchiveURL string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"` + // Package query that is applied to mirror packages + Filter string ` json:"Filter" example:"xserver-xorg"` + // Limit mirror to those architectures, if not specified aptly would fetch all architectures + Architectures []string ` json:"Architectures" example:"amd64"` + // Components to mirror, if not specified aptly would fetch all components + Components []string ` json:"Components" example:"main"` + // Gpg keyring(s) for verifing Release file + Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"` + // Set "true" to include dependencies of matching packages when filtering + FilterWithDeps bool ` json:"FilterWithDeps"` + // Set "true" to mirror source packages + DownloadSources bool ` json:"DownloadSources"` + // Set "true" to mirror udeb files + DownloadUdebs bool ` json:"DownloadUdebs"` + // Set "true" to skip checking if the given components are in the Release file + SkipComponentCheck bool ` json:"SkipComponentCheck"` + // Set "true" to skip checking if the given architectures are in the Release file + SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"` + // Set "true" to ignore checksum errors + IgnoreChecksums bool ` json:"IgnoreChecksums"` + // Set "true" to skip the verification of Release file signatures + IgnoreSignatures bool ` json:"IgnoreSignatures"` + // Set "true" to force a mirror update even if another process is already updating the mirror (use with caution!) + ForceUpdate bool ` json:"ForceUpdate"` + // Set "true" to skip downloading already downloaded packages + SkipExistingPackages bool ` json:"SkipExistingPackages"` +} + +// @Summary Update Mirror +// @Description **Update Mirror and download packages** +// @Tags Mirrors +// @Param name path string true "mirror name to update" +// @Consume json +// @Param request body mirrorUpdateParams true "Parameters" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 {object} task.ProcessReturnValue "Mirror was updated successfully" +// @Success 202 {object} task.Task "Mirror is being updated" +// @Failure 400 {object} Error "Unable to determine list of architectures" +// @Failure 404 {object} Error "Mirror not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/mirrors/{name} [put] func apiMirrorsUpdate(c *gin.Context) { var ( err error remote *deb.RemoteRepo + b mirrorUpdateParams ) - var b struct { - Name string - ArchiveURL string - Filter string - Architectures []string - Components []string - Keyrings []string - FilterWithDeps bool - DownloadSources bool - DownloadUdebs bool - SkipComponentCheck bool - IgnoreChecksums bool - IgnoreSignatures bool - ForceUpdate bool - SkipExistingPackages bool - } - collectionFactory := context.NewCollectionFactory() collection := collectionFactory.RemoteRepoCollection() remote, err = collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } @@ -304,12 +397,14 @@ func apiMirrorsUpdate(c *gin.Context) { b.DownloadUdebs = remote.DownloadUdebs b.DownloadSources = remote.DownloadSources b.SkipComponentCheck = remote.SkipComponentCheck + b.SkipArchitectureCheck = remote.SkipArchitectureCheck b.FilterWithDeps = remote.FilterWithDeps b.Filter = remote.Filter b.Architectures = remote.Architectures b.Components = remote.Components + b.IgnoreSignatures = context.Config().GpgDisableVerify - log.Printf("%s: Starting mirror update\n", b.Name) + log.Info().Msgf("%s: Starting mirror update", b.Name) if c.Bind(&b) != nil { return @@ -318,14 +413,14 @@ func apiMirrorsUpdate(c *gin.Context) { if b.Name != remote.Name { _, err = collection.ByName(b.Name) if err == nil { - c.AbortWithError(409, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)) + AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)) return } } if b.DownloadUdebs != remote.DownloadUdebs { if remote.IsFlat() && b.DownloadUdebs { - c.AbortWithError(400, fmt.Errorf("unable to update: flat mirrors don't support udebs")) + AbortWithJSONError(c, 400, fmt.Errorf("unable to update: flat mirrors don't support udebs")) return } } @@ -338,14 +433,15 @@ func apiMirrorsUpdate(c *gin.Context) { remote.DownloadUdebs = b.DownloadUdebs remote.DownloadSources = b.DownloadSources remote.SkipComponentCheck = b.SkipComponentCheck + remote.SkipArchitectureCheck = b.SkipArchitectureCheck remote.FilterWithDeps = b.FilterWithDeps remote.Filter = b.Filter remote.Architectures = b.Architectures remote.Components = b.Components - verifier, err := getVerifier(b.IgnoreSignatures, b.Keyrings) + verifier, err := getVerifier(b.Keyrings) if err != nil { - c.AbortWithError(400, fmt.Errorf("unable to initialize GPG verifier: %s", err)) + AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err)) return } @@ -353,7 +449,7 @@ func apiMirrorsUpdate(c *gin.Context) { maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { downloader := context.NewDownloader(out) - err := remote.Fetch(downloader, verifier) + err := remote.Fetch(downloader, verifier, b.IgnoreSignatures) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) } @@ -365,7 +461,7 @@ func apiMirrorsUpdate(c *gin.Context) { } } - err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.SkipComponentCheck) + err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, b.SkipComponentCheck) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) } @@ -458,7 +554,7 @@ func apiMirrorsUpdate(c *gin.Context) { } }() - log.Printf("%s: Spawning background processes...\n", b.Name) + log.Info().Msgf("%s: Spawning background processes...", b.Name) var wg sync.WaitGroup for i := 0; i < context.Config().DownloadConcurrency; i++ { wg.Add(1) @@ -476,7 +572,16 @@ func apiMirrorsUpdate(c *gin.Context) { var e error // provision download location - task.TempDownPath, e = context.PackagePool().(aptly.LocalPackagePool).GenerateTempPath(task.File.Filename) + if pp, ok := context.PackagePool().(aptly.LocalPackagePool); ok { + task.TempDownPath, e = pp.GenerateTempPath(task.File.Filename) + } else { + var file *os.File + file, e = os.CreateTemp("", task.File.Filename) + if e == nil { + task.TempDownPath = file.Name() + file.Close() + } + } if e != nil { pushError(e) continue @@ -494,6 +599,20 @@ func apiMirrorsUpdate(c *gin.Context) { continue } + // and import it back to the pool + task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, collectionFactory.ChecksumCollection(nil)) + if err != nil { + //return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err) + pushError(err) + continue + } + + // update "attached" files if any + for _, additionalAtask := range task.Additional { + additionalAtask.File.PoolPath = task.File.PoolPath + additionalAtask.File.Checksums = task.File.Checksums + } + task.Done = true taskFinished <- task case <-context.Done(): @@ -505,32 +624,22 @@ func apiMirrorsUpdate(c *gin.Context) { } // Wait for all download goroutines to finish - log.Printf("%s: Waiting for background processes to finish...\n", b.Name) + log.Info().Msgf("%s: Waiting for background processes to finish...", b.Name) wg.Wait() - log.Printf("%s: Background processes finished\n", b.Name) + log.Info().Msgf("%s: Background processes finished", b.Name) close(taskFinished) - for idx := range queue { + defer func() { + for _, task := range queue { + if task.TempDownPath == "" { + continue + } - atask := &queue[idx] - - if !atask.Done { - // download not finished yet - continue + if err := os.Remove(task.TempDownPath); err != nil && !os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "Failed to delete %s: %v\n", task.TempDownPath, err) + } } - - // and import it back to the pool - atask.File.PoolPath, err = context.PackagePool().Import(atask.TempDownPath, atask.File.Filename, &atask.File.Checksums, true, collectionFactory.ChecksumCollection(nil)) - if err != nil { - return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err) - } - - // update "attached" files if any - for _, additionalAtask := range atask.Additional { - additionalAtask.File.PoolPath = atask.File.PoolPath - additionalAtask.File.Checksums = atask.File.Checksums - } - } + }() select { case <-context.Done(): @@ -539,18 +648,18 @@ func apiMirrorsUpdate(c *gin.Context) { } if len(errors) > 0 { - log.Printf("%s: Unable to update because of previous errors\n", b.Name) + log.Info().Msgf("%s: Unable to update because of previous errors", b.Name) return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n ")) } - log.Printf("%s: Finalizing download\n", b.Name) + log.Info().Msgf("%s: Finalizing download...", b.Name) remote.FinalizeDownload(collectionFactory, out) err = collectionFactory.RemoteRepoCollection().Update(remote) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) } - log.Printf("%s: Mirror updated successfully!\n", b.Name) + log.Info().Msgf("%s: Mirror updated successfully", b.Name) return &task.ProcessReturnValue{Code: http.StatusNoContent, Value: nil}, nil }) } diff --git a/api/packages.go b/api/packages.go index e560076a..e29d1fa5 100644 --- a/api/packages.go +++ b/api/packages.go @@ -1,17 +1,41 @@ package api import ( + _ "github.com/aptly-dev/aptly/deb" // for swagger "github.com/gin-gonic/gin" ) -// GET /api/packages/:key +// @Summary Get Package Info +// @Description **Show information about package by package key** +// @Description Package keys could be obtained from various GET .../packages APIs. +// @Tags Packages +// @Produce json +// @Param key path string true "package key (unique package identifier)" +// @Success 200 {object} deb.Package "OK" +// @Failure 404 {object} Error "Not Found" +// @Router /api/packages/{key} [get] func apiPackagesShow(c *gin.Context) { collectionFactory := context.NewCollectionFactory() p, err := collectionFactory.PackageCollection().ByKey([]byte(c.Params.ByName("key"))) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } c.JSON(200, p) } + +// @Summary List Packages +// @Description **Get list of packages** +// @Tags Packages +// @Consume json +// @Produce json +// @Param q query string false "search query" +// @Param format query string false "format: `details` for more detailed information" +// @Success 200 {array} string "List of packages" +// @Router /api/packages [get] +func apiPackages(c *gin.Context) { + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PackageCollection() + showPackages(c, collection.AllPackageRefs(), collectionFactory) +} diff --git a/api/packages_test.go b/api/packages_test.go new file mode 100644 index 00000000..221a5ab1 --- /dev/null +++ b/api/packages_test.go @@ -0,0 +1,18 @@ +package api + +import ( + . "gopkg.in/check.v1" +) + +type PackagesSuite struct { + ApiSuite +} + +var _ = Suite(&PackagesSuite{}) + +func (s *PackagesSuite) TestPackagesGetMaximumVersion(c *C) { + response, err := s.HTTPRequest("GET", "/api/repos/dummy/packages?maximumVersion=1", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) + c.Check(response.Body.String(), Equals, "[]") +} diff --git a/api/publish.go b/api/publish.go index d2287dbd..e19afb17 100644 --- a/api/publish.go +++ b/api/publish.go @@ -13,18 +13,29 @@ import ( "github.com/gin-gonic/gin" ) -// SigningOptions is a shared between publish API GPG options structure -type SigningOptions struct { - Skip bool - Batch bool - GpgKey string - Keyring string - SecretKeyring string - Passphrase string - PassphraseFile string +type signingParams struct { + // Don't sign published repository + Skip bool ` json:"Skip" example:"false"` + // GPG key ID to use when signing the release, if not specified default key is used + GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"` + // GPG keyring to use (instead of default) + Keyring string ` json:"Keyring" example:"trustedkeys.gpg"` + // GPG secret keyring to use (instead of default) Note: depreciated with gpg2 + SecretKeyring string ` json:"SecretKeyring" example:""` + // GPG passphrase to unlock private key (possibly insecure) + Passphrase string ` json:"Passphrase" example:"verysecure"` + // GPG passphrase file to unlock private key (possibly insecure) + PassphraseFile string ` json:"PassphraseFile" example:"/etc/aptly.passphrase"` } -func getSigner(options *SigningOptions) (pgp.Signer, error) { +type sourceParams struct { + // Name of the component + Component string `binding:"required" json:"Component" example:"main"` + // Name of the local repository/snapshot + Name string `binding:"required" json:"Name" example:"snap1"` +} + +func getSigner(options *signingParams) (pgp.Signer, error) { if options.Skip { return nil, nil } @@ -33,7 +44,9 @@ func getSigner(options *SigningOptions) (pgp.Signer, error) { signer.SetKey(options.GpgKey) signer.SetKeyRing(options.Keyring, options.SecretKeyring) signer.SetPassphrase(options.Passphrase, options.PassphraseFile) - signer.SetBatch(options.Batch) + + // If Batch is false, GPG will ask for passphrase on stdin, which would block the api process + signer.SetBatch(true) err := signer.Init() if err != nil { @@ -43,87 +56,185 @@ func getSigner(options *SigningOptions) (pgp.Signer, error) { return signer, nil } -// Replace '_' with '/' and double '__' with single '_' -func parseEscapedPath(path string) string { +// Replace '_' with '/' and double '__' with single '_', SanitizePath +func slashEscape(path string) string { result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1) + result = utils.SanitizePath(result) if result == "" { result = "." } return result } -// GET /publish +// @Summary List Published Repositories +// @Description **Get list of published repositories** +// @Description +// @Description Return list of published repositories including detailed information. +// @Description +// @Description See also: `aptly publish list` +// @Tags Publish +// @Produce json +// @Success 200 {array} deb.PublishedRepo +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish [get] func apiPublishList(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.PublishedRepoCollection() - result := make([]*deb.PublishedRepo, 0, collection.Len()) + repos := make([]*deb.PublishedRepo, 0, collection.Len()) err := collection.ForEach(func(repo *deb.PublishedRepo) error { - err := collection.LoadComplete(repo, collectionFactory) + err := collection.LoadShallow(repo, collectionFactory) if err != nil { return err } - result = append(result, repo) + repos = append(repos, repo) return nil }) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, http.StatusInternalServerError, err) return } - c.JSON(200, result) + c.JSON(http.StatusOK, repos) } -// POST /publish/:prefix -func apiPublishRepoOrSnapshot(c *gin.Context) { - param := parseEscapedPath(c.Params.ByName("prefix")) +// @Summary Show Published Repository +// @Description **Get published repository information** +// @Description +// @Description Show detailed information of a published repository. +// @Description +// @Description See also: `aptly publish show` +// @Tags Publish +// @Produce json +// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambigious in URLs" +// @Param distribution path string true "distribution name" +// @Success 200 {object} deb.PublishedRepo +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution} [get] +func apiPublishShow(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) - var b struct { - SourceKind string `binding:"required"` - Sources []struct { - Component string - Name string `binding:"required"` - } `binding:"required"` - Distribution string - Label string - Origin string - NotAutomatic string - ButAutomaticUpgrades string - ForceOverwrite bool - SkipContents *bool - SkipBz2 *bool - Architectures []string - Signing SigningOptions - AcquireByHash *bool + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err)) + return } + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to show: %s", err)) + return + } + + c.JSON(http.StatusOK, published) +} + +type publishedRepoCreateParams struct { + // 'local' for local repositories and 'snapshot' for snapshots + SourceKind string `binding:"required" json:"SourceKind" example:"snapshot"` + // List of 'Component/Name' objects, 'Name' is either local repository or snapshot name + 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 Label: field in published repository stanza + Label string ` json:"Label" example:""` + // Value of Origin: field in published repository stanza + Origin string ` json:"Origin" example:""` + // when publishing, overwrite files in pool/ directory without notice + ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` + // Override list of published architectures + Architectures []string ` json:"Architectures" example:"amd64,armhf"` + // GPG options + Signing signingParams ` json:"Signing"` + // Setting to yes indicates to the package manager to not install or upgrade packages from the repository without user consent + NotAutomatic string ` json:"NotAutomatic" example:""` + // setting to yes excludes upgrades from the NotAutomic setting + ButAutomaticUpgrades string ` json:"ButAutomaticUpgrades" example:""` + // Don't generate contents indexes + SkipContents *bool ` json:"SkipContents" example:"false"` + // Don't remove unreferenced files in prefix/component + SkipCleanup *bool ` json:"SkipCleanup" example:"false"` + // Skip bz2 compression for index files + SkipBz2 *bool ` json:"SkipBz2" example:"false"` + // Provide index files by hash + AcquireByHash *bool ` json:"AcquireByHash" example:"false"` + // Enable multiple packages with the same filename in different distributions + MultiDist *bool ` json:"MultiDist" example:"false"` +} + +// @Summary Create Published Repository +// @Description **Publish a local repository or snapshot** +// @Description +// @Description Create a published repository. +// @Description +// @Description The prefix may contain a storage specifier, e.g. `s3:packages/`, or it may also be empty to publish to the root directory. +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos +// @Description {"Architectures":["i386"],"Distribution":"wheezy","Label":"","Origin":"","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""} +// @Description ``` +// @Description +// @Description See also: `aptly publish create` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Param request body publishedRepoCreateParams true "Parameters" +// @Produce json +// @Success 201 {object} deb.PublishedRepo +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Source not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix} [post] +func apiPublishRepoOrSnapshot(c *gin.Context) { + var ( + b publishedRepoCreateParams + components []string + names []string + sources []interface{} + resources []string + ) + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + if c.Bind(&b) != nil { return } + b.Distribution = utils.SanitizePath(b.Distribution) + + var archs []string + for _, arch := range b.Architectures { + archs = append(archs, utils.SanitizePath(arch)) + } + b.Architectures = archs + signer, err := getSigner(&b.Signing) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to initialize GPG signer: %s", err)) + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err)) return } if len(b.Sources) == 0 { - c.AbortWithError(400, fmt.Errorf("unable to publish: soures are empty")) + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to publish: sources are empty")) return } - var components []string - var names []string - var sources []interface{} - var resources []string collectionFactory := context.NewCollectionFactory() - if b.SourceKind == "snapshot" { + if b.SourceKind == deb.SourceSnapshot { var snapshot *deb.Snapshot snapshotCollection := collectionFactory.SnapshotCollection() @@ -134,17 +245,11 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { snapshot, err = snapshotCollection.ByName(source.Name) if err != nil { - c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err)) + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err)) return } resources = append(resources, string(snapshot.ResourceKey())) - err = snapshotCollection.LoadComplete(snapshot) - if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err)) - return - } - sources = append(sources, snapshot) } } else if b.SourceKind == deb.SourceLocalRepo { @@ -158,33 +263,27 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { localRepo, err = localCollection.ByName(source.Name) if err != nil { - c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err)) + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err)) return } resources = append(resources, string(localRepo.Key())) - err = localCollection.LoadComplete(localRepo) - if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err)) - } - sources = append(sources, localRepo) } } else { - c.AbortWithError(400, fmt.Errorf("unknown SourceKind")) + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unknown SourceKind")) return } - published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory) - if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err)) - return + multiDist := false + if b.MultiDist != nil { + multiDist = *b.MultiDist } - resources = append(resources, string(published.Key())) collection := collectionFactory.PublishedRepoCollection() - taskName := fmt.Sprintf("Publish %s: %s", b.SourceKind, strings.Join(names, ", ")) + taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"", + b.SourceKind, param, b.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`)) maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { taskDetail := task.PublishDetail{ Detail: detail, @@ -194,6 +293,29 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { PublishDetail: taskDetail, } + for _, source := range sources { + switch s := source.(type) { + case *deb.Snapshot: + snapshotCollection := collectionFactory.SnapshotCollection() + err = snapshotCollection.LoadComplete(s) + case *deb.LocalRepo: + localCollection := collectionFactory.LocalRepoCollection() + err = localCollection.LoadComplete(s) + default: + err = fmt.Errorf("unexpected type for source: %T", source) + } + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) + } + } + + published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, multiDist) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) + } + + resources = append(resources, string(published.Key())) + if b.Origin != "" { published.Origin = b.Origin } @@ -211,7 +333,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { } published.SkipBz2 = context.Config().SkipBz2Publishing - if b.SkipContents != nil { + if b.SkipBz2 != nil { published.SkipBz2 = *b.SkipBz2 } @@ -225,7 +347,7 @@ 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) } - err := published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite) + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath()) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) } @@ -239,24 +361,56 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { }) } -// PUT /publish/:prefix/:distribution -func apiPublishUpdateSwitch(c *gin.Context) { - param := parseEscapedPath(c.Params.ByName("prefix")) - storage, prefix := deb.ParsePrefix(param) - distribution := c.Params.ByName("distribution") +type publishedRepoUpdateSwitchParams struct { + // when publishing, overwrite files in pool/ directory without notice + ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` + // GPG options + Signing signingParams ` json:"Signing"` + // Don't generate contents indexes + SkipContents *bool ` json:"SkipContents" example:"false"` + // Skip bz2 compression for index files + SkipBz2 *bool ` json:"SkipBz2" example:"false"` + // Don't remove unreferenced files in prefix/component + SkipCleanup *bool ` json:"SkipCleanup" example:"false"` + // only when updating published snapshots, list of objects 'Component/Name' + Snapshots []sourceParams ` json:"Snapshots"` + // Provide index files by hash + AcquireByHash *bool ` json:"AcquireByHash" example:"false"` + // Enable multiple packages with the same filename in different distributions + MultiDist *bool ` json:"MultiDist" example:"false"` +} - var b struct { - ForceOverwrite bool - Signing SigningOptions - SkipContents *bool - SkipBz2 *bool - SkipCleanup *bool - Snapshots []struct { - Component string `binding:"required"` - Name string `binding:"required"` - } - AcquireByHash *bool - } +// @Summary Update Published Repository +// @Description **Update a published repository** +// @Description +// @Description Update a published local repository or switch snapshot. +// @Description +// @Description For published local repositories: +// @Description * update to match local repository contents +// @Description +// @Description For published snapshots: +// @Description * switch components to new snapshot +// @Description +// @Description See also: `aptly publish update` / `aptly publish switch` +// @Tags Publish +// @Produce json +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Param request body publishedRepoUpdateSwitchParams true "Parameters" +// @Produce json +// @Success 200 {object} deb.PublishedRepo +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository or source not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution} [put] +func apiPublishUpdateSwitch(c *gin.Context) { + var b publishedRepoUpdateSwitchParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) if c.Bind(&b) != nil { return @@ -264,64 +418,35 @@ func apiPublishUpdateSwitch(c *gin.Context) { signer, err := getSigner(&b.Signing) if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to initialize GPG signer: %s", err)) + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err)) return } collectionFactory := context.NewCollectionFactory() collection := collectionFactory.PublishedRepoCollection() + snapshotCollection := collectionFactory.SnapshotCollection() published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) if err != nil { - c.AbortWithError(404, fmt.Errorf("unable to update: %s", err)) + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) return } - err = collection.LoadComplete(published, collectionFactory) - if err != nil { - c.AbortWithError(500, fmt.Errorf("unable to update: %s", err)) - return - } - - var updatedComponents []string - var updatedSnapshots []string - var resources []string if published.SourceKind == deb.SourceLocalRepo { if len(b.Snapshots) > 0 { - c.AbortWithError(400, fmt.Errorf("snapshots shouldn't be given when updating local repo")) + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo")) return } - updatedComponents = published.Components() - for _, component := range updatedComponents { - published.UpdateLocalRepo(component) - } - } else if published.SourceKind == "snapshot" { - publishedComponents := published.Components() + } else if published.SourceKind == deb.SourceSnapshot { for _, snapshotInfo := range b.Snapshots { - if !utils.StrSliceHasItem(publishedComponents, snapshotInfo.Component) { - c.AbortWithError(404, fmt.Errorf("component %s is not in published repository", snapshotInfo.Component)) - return - } - - snapshotCollection := collectionFactory.SnapshotCollection() - snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name) + _, err2 := snapshotCollection.ByName(snapshotInfo.Name) if err2 != nil { - c.AbortWithError(404, err2) + AbortWithJSONError(c, http.StatusNotFound, err2) return } - - err2 = snapshotCollection.LoadComplete(snapshot) - if err2 != nil { - c.AbortWithError(500, err2) - return - } - - published.UpdateSnapshot(snapshotInfo.Component, snapshot) - updatedComponents = append(updatedComponents, snapshotInfo.Component) - updatedSnapshots = append(updatedSnapshots, snapshot.Name) } } else { - c.AbortWithError(500, fmt.Errorf("unknown published repository type")) + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type")) return } @@ -337,10 +462,578 @@ func apiPublishUpdateSwitch(c *gin.Context) { published.AcquireByHash = *b.AcquireByHash } - resources = append(resources, string(published.Key())) - taskName := fmt.Sprintf("Update published %s (%s): %s", published.SourceKind, strings.Join(updatedComponents, " "), strings.Join(updatedSnapshots, ", ")) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { - err := published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite) + if b.MultiDist != nil { + published.MultiDist = *b.MultiDist + } + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err) + } + + revision := published.ObtainRevision() + sources := revision.Sources + + if published.SourceKind == deb.SourceSnapshot { + for _, snapshotInfo := range b.Snapshots { + component := snapshotInfo.Component + name := snapshotInfo.Name + sources[component] = name + } + } + + result, err := published.Update(collectionFactory, out) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err) + } + + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath()) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err) + } + + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + if b.SkipCleanup == nil || !*b.SkipCleanup { + cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources)) + cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...) + err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) + } + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil + }) +} + +// @Summary Delete Published Repository +// @Description **Delete a published repository** +// @Description +// @Description Delete a distribution of a published repository and remove associated files. +// @Description +// @Description If no other published repositories share the same prefix, all files inside the prefix will be removed. +// @Description +// @Description See also: `aptly publish drop` +// @Tags Publish +// @Produce json +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param force query int true "force: 1 to enable" +// @Param skipCleanup query int true "skipCleanup: 1 to enable" +// @Param _async query bool false "Run in background and return task object" +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution} [delete] +func apiPublishDrop(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + force := c.Request.URL.Query().Get("force") == "1" + skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1" + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to drop: %s", err)) + return + } + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Delete published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err := collection.Remove(context, storage, prefix, distribution, + collectionFactory, out, force, skipCleanup) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +// @Summary Add Source Component +// @Description **Add a source component to a published repo** +// @Description +// @Description Add a component of a snapshot or local repository to be published. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source add` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Param request body sourceParams true "Parameters" +// @Produce json +// @Success 201 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [post] +func apiPublishAddSource(c *gin.Context) { + var b sourceParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to create: %s", err)) + return + } + + if c.Bind(&b) != nil { + return + } + + revision := published.ObtainRevision() + sources := revision.Sources + + component := b.Component + name := b.Name + + _, exists := sources[component] + if exists { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to create: Component '%s' already exists", component)) + return + } + + sources[component] = name + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusCreated, Value: gin.H{}}, nil + }) +} + +// @Summary List Pending Changes +// @Description **List source component changes to be applied** +// @Description +// @Description Return added, removed or changed components of snapshots or local repository to be published. +// @Description +// @Description The changes will be applied by a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source list` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Produce json +// @Success 200 {array} []deb.SourceEntry +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository pending changes not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [get] +func apiPublishListChanges(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to show: %s", err)) + return + } + + revision := published.Revision + if revision == nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: No source changes exist")) + return + } + + c.JSON(http.StatusOK, revision.SourceList()) +} + +// @Summary Replace Source Components +// @Description **Replace the source components of a published repository** +// @Description +// @Description Sets the components of snapshots or local repositories to be published. Existing Sourced will be replaced. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source replace` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Param request body []sourceParams true "Parameters" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [put] +func apiPublishSetSources(c *gin.Context) { + var b []sourceParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err)) + return + } + + if c.Bind(&b) != nil { + return + } + + revision := published.ObtainRevision() + sources := make(map[string]string, len(b)) + revision.Sources = sources + + for _, source := range b { + component := source.Component + name := source.Name + sources[component] = name + } + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: revision.SourceList()}, nil + }) +} + +// @Summary Discard Pending Changes +// @Description **Discard pending source component changes of a published repository** +// @Description +// @Description Remove all pending changes what would be applied with a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source drop` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [delete] +func apiPublishDropChanges(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err)) + return + } + + published.DropRevision() + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +// @Summary Update Source Component +// @Description **Update the source component of a published repository** +// @Description +// @Description Update a component of a snapshot or local repository to be published. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source update` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param component path string true "component name" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Param request body sourceParams true "Parameters" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository/component not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources/{component} [put] +func apiPublishUpdateSource(c *gin.Context) { + var b sourceParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + component := slashEscape(c.Params.ByName("component")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err)) + return + } + + revision := published.ObtainRevision() + sources := revision.Sources + + _, exists := sources[component] + if !exists { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: Component '%s' does not exist", component)) + return + } + + b.Component = component + b.Name = revision.Sources[component] + + if c.Bind(&b) != nil { + return + } + + if b.Component != component { + delete(sources, component) + } + + component = b.Component + name := b.Name + sources[component] = name + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +// @Summary Remove Source Component +// @Description **Remove a source component from a published repo** +// @Description +// @Description Remove a source component (snapshot / local repo) from a published repository. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source remove` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param component path string true "component name" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources/{component} [delete] +func apiPublishRemoveSource(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + component := slashEscape(c.Params.ByName("component")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err)) + return + } + + revision := published.ObtainRevision() + sources := revision.Sources + + _, exists := sources[component] + if !exists { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: Component '%s' does not exist", component)) + return + } + + delete(sources, component) + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +type publishedRepoUpdateParams struct { + // when publishing, overwrite files in pool/ directory without notice + ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` + // GPG options + Signing signingParams ` json:"Signing"` + // Don't generate contents indexes + SkipContents *bool ` json:"SkipContents" example:"false"` + // Skip bz2 compression for index files + SkipBz2 *bool ` json:"SkipBz2" example:"false"` + // Don't remove unreferenced files in prefix/component + SkipCleanup *bool ` json:"SkipCleanup" example:"false"` + // Provide index files by hash + AcquireByHash *bool ` json:"AcquireByHash" example:"false"` + // Enable multiple packages with the same filename in different distributions + MultiDist *bool ` json:"MultiDist" example:"false"` +} + +// @Summary Update Published Repository +// @Description **Update a published repository** +// @Description +// @Description Publish pending source component changes which were added with `Add/Remove/Replace Source Components` +// @Description +// @Description See also: `aptly publish update` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Param request body publishedRepoUpdateParams true "Parameters" +// @Produce json +// @Success 200 {object} deb.PublishedRepo +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository/component not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/update [post] +func apiPublishUpdate(c *gin.Context) { + var b publishedRepoUpdateParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + if c.Bind(&b) != nil { + return + } + + signer, err := getSigner(&b.Signing) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err)) + return + } + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err)) + return + } + + if b.SkipContents != nil { + published.SkipContents = *b.SkipContents + } + + if b.SkipBz2 != nil { + published.SkipBz2 = *b.SkipBz2 + } + + if b.AcquireByHash != nil { + published.AcquireByHash = *b.AcquireByHash + } + + if b.MultiDist != nil { + published.MultiDist = *b.MultiDist + } + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + result, err := published.Update(collectionFactory, out) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) + } + + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath()) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) } @@ -351,8 +1044,9 @@ func apiPublishUpdateSwitch(c *gin.Context) { } if b.SkipCleanup == nil || !*b.SkipCleanup { - err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents, - context.GetPublishedStorage(storage), collectionFactory, out) + cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources)) + cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...) + err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) } @@ -361,35 +1055,3 @@ func apiPublishUpdateSwitch(c *gin.Context) { return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil }) } - -// DELETE /publish/:prefix/:distribution -func apiPublishDrop(c *gin.Context) { - force := c.Request.URL.Query().Get("force") == "1" - skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1" - - param := parseEscapedPath(c.Params.ByName("prefix")) - storage, prefix := deb.ParsePrefix(param) - distribution := c.Params.ByName("distribution") - - collectionFactory := context.NewCollectionFactory() - collection := collectionFactory.PublishedRepoCollection() - - published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("unable to drop: %s", err)) - return - } - - resources := []string{string(published.Key())} - - taskName := fmt.Sprintf("Delete published %s (%s)", prefix, distribution) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { - err := collection.Remove(context, storage, prefix, distribution, - collectionFactory, out, force, skipCleanup) - if err != nil { - return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err) - } - - return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil - }) -} diff --git a/api/repos.go b/api/repos.go index 24b2530d..4cc04f69 100644 --- a/api/repos.go +++ b/api/repos.go @@ -5,6 +5,7 @@ import ( "net/http" "os" "path/filepath" + "sort" "strings" "text/template" @@ -17,7 +18,59 @@ import ( "github.com/gin-gonic/gin" ) -// GET /api/repos +// @Summary Serve HTML Listing +// @Description If ServeInAPIMode is enabled in aptly config, +// @Description this endpoint is enabled which returns an HTML listing of each repo that can be browsed +// @Tags Repos +// @Produce html +// @Success 200 {object} string "HTML" +// @Router /api/repos [get] +func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") + c.Writer.Flush() + c.Writer.WriteString("
\n")
+		if len(localRepos) == 0 {
+			c.Writer.WriteString("default\n")
+		}
+		for publishPrefix := range localRepos {
+			c.Writer.WriteString(fmt.Sprintf("%[1]s\n", publishPrefix))
+		}
+		c.Writer.WriteString("
") + c.Writer.Flush() + } +} + +// @Summary Serve Packages +// @Description If ServeInAPIMode is enabled in aptly config, +// @Description this api serves a specified package from storage +// @Tags Repos +// @Param storage path string true "Storage" +// @Param pkgPath path string true "Package Path" allowReserved=true +// @Produce json +// @Success 200 "" +// @Router /api/{storage}/{pkgPath} [get] +func reposServeInAPIMode(c *gin.Context) { + pkgpath := c.Param("pkgPath") + + storage := c.Param("storage") + if storage == "-" { + storage = "" + } else { + storage = "filesystem:" + storage + } + + publicPath := context.GetPublishedStorage(storage).(aptly.FileSystemPublishedStorage).PublicPath() + c.FileFromFS(pkgpath, http.Dir(publicPath)) +} + +// @Summary List Repositories +// @Description **Get list of available repos** +// @Description Each repo is returned as in “show” API. +// @Tags Repos +// @Produce json +// @Success 200 {array} deb.LocalRepo +// @Router /api/repos [get] func apiReposList(c *gin.Context) { result := []*deb.LocalRepo{} @@ -31,14 +84,39 @@ func apiReposList(c *gin.Context) { c.JSON(200, result) } -// POST /api/repos +type repoCreateParams struct { + // Name of repository to create + Name string `binding:"required" json:"Name" example:"repo1"` + // Text describing the repository (optional) + Comment string ` json:"Comment" example:"this is a repo"` + // Default distribution when publishing from this local repo + DefaultDistribution string ` json:"DefaultDistribution" example:"stable"` + // Default component when publishing from this local repo + DefaultComponent string ` json:"DefaultComponent" example:"main"` + // Snapshot name to create repoitory from (optional) + FromSnapshot string ` json:"FromSnapshot" example:""` +} + +// @Summary Create Repository +// @Description **Create a local repository** +// @Description +// @Description Distribution and component are used as defaults when publishing repo either directly or via snapshot. +// @Description +// @Description ``` +// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Name": "aptly-repo"}' http://localhost:8080/api/repos +// @Description {"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""} +// @Description ``` +// @Tags Repos +// @Produce json +// @Consume json +// @Param request body repoCreateParams true "Parameters" +// @Success 201 {object} deb.LocalRepo +// @Failure 404 {object} Error "Source snapshot not found" +// @Failure 409 {object} Error "Local repo already exists" +// @Failure 500 {object} Error "Internal error" +// @Router /api/repos [post] func apiReposCreate(c *gin.Context) { - var b struct { - Name string `binding:"required"` - Comment string - DefaultDistribution string - DefaultComponent string - } + var b repoCreateParams if c.Bind(&b) != nil { return @@ -49,25 +127,65 @@ func apiReposCreate(c *gin.Context) { repo.DefaultDistribution = b.DefaultDistribution collectionFactory := context.NewCollectionFactory() - collection := collectionFactory.LocalRepoCollection() - err := collection.Add(repo) - if err != nil { - c.AbortWithError(400, err) + + if b.FromSnapshot != "" { + var snapshot *deb.Snapshot + + snapshotCollection := collectionFactory.SnapshotCollection() + + snapshot, err := snapshotCollection.ByName(b.FromSnapshot) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err)) + return + } + + err = snapshotCollection.LoadComplete(snapshot) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to load source snapshot: %s", err)) + return + } + + repo.UpdateRefList(snapshot.RefList()) + } + + localRepoCollection := collectionFactory.LocalRepoCollection() + + if _, err := localRepoCollection.ByName(b.Name); err == nil { + AbortWithJSONError(c, http.StatusConflict, fmt.Errorf("local repo with name %s already exists", b.Name)) return } - c.JSON(201, repo) -} - -// PUT /api/repos/:name -func apiReposEdit(c *gin.Context) { - var b struct { - Name *string - Comment *string - DefaultDistribution *string - DefaultComponent *string + err := localRepoCollection.Add(repo) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, err) + return } + c.JSON(http.StatusCreated, repo) +} + +type reposEditParams struct { + // Name of repository to modify + Name *string `binding:"required" json:"Name" example:"repo1"` + // Change Comment of repository + Comment *string ` json:"Comment" example:"example repo"` + // Change Default Distribution for publishing + DefaultDistribution *string ` json:"DefaultDistribution" example:""` + // Change Devault Component for publishing + DefaultComponent *string ` json:"DefaultComponent" example:""` +} + +// @Summary Update Repository +// @Description **Update local repository meta information** +// @Tags Repos +// @Produce json +// @Param request body reposEditParams true "Parameters" +// @Success 200 {object} deb.LocalRepo "msg" +// @Failure 404 {object} Error "Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/repos/{name} [put] +func apiReposEdit(c *gin.Context) { + var b reposEditParams if c.Bind(&b) != nil { return } @@ -77,7 +195,7 @@ func apiReposEdit(c *gin.Context) { repo, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } @@ -85,7 +203,7 @@ func apiReposEdit(c *gin.Context) { _, err := collection.ByName(*b.Name) if err == nil { // already exists - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } repo.Name = *b.Name @@ -102,7 +220,7 @@ func apiReposEdit(c *gin.Context) { err = collection.Update(repo) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } @@ -110,20 +228,39 @@ func apiReposEdit(c *gin.Context) { } // GET /api/repos/:name +// @Summary Get Repository Info +// @Description Returns basic information about local repository. +// @Tags Repos +// @Produce json +// @Param name path string true "Repository name" +// @Success 200 {object} deb.LocalRepo +// @Failure 404 {object} Error "Repository not found" +// @Router /api/repos/{name} [get] func apiReposShow(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.LocalRepoCollection() repo, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } c.JSON(200, repo) } -// DELETE /api/repos/:name +// @Summary Delete Repository +// @Description Drop/delete a repo +// @Description Cannot drop repos that are published. +// @Description Needs force=1 to drop repos used as source by other repos. +// @Tags Repos +// @Produce json +// @Param _async query bool false "Run in background and return task object" +// @Param force query int false "force: 1 to enable" +// @Success 200 {object} task.ProcessReturnValue "Repo object" +// @Failure 404 {object} Error "Not Found" +// @Failure 404 {object} Error "Repo Conflict" +// @Router /api/repos/{name} [delete] func apiReposDrop(c *gin.Context) { force := c.Request.URL.Query().Get("force") == "1" name := c.Params.ByName("name") @@ -135,13 +272,13 @@ func apiReposDrop(c *gin.Context) { repo, err := collection.ByName(name) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } resources := []string{string(repo.Key())} taskName := fmt.Sprintf("Delete repo %s", name) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { published := publishedCollection.ByLocalRepo(repo) if len(published) > 0 { return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published") @@ -158,31 +295,54 @@ func apiReposDrop(c *gin.Context) { }) } -// GET /api/repos/:name/packages +// @Summary List Repo Packages +// @Description **Return a list of packages present in the repo** +// @Description +// @Description If `q` query parameter is missing, return all packages, otherwise return packages that match q +// @Description +// @Description **Example:** +// @Description ``` +// @Description $ curl http://localhost:8080/api/repos/aptly-repo/packages +// @Description ["Pi386 aptly 0.8 966561016b44ed80"] +// @Description ``` +// @Tags Repos +// @Produce json +// @Param name path string true "Snapshot to search" +// @Param q query string true "Package query (e.g Name%20(~%20matlab))" +// @Param withDeps query string true "Set to 1 to include dependencies when evaluating package query" +// @Param format query string true "Set to 'details' to return extra info about each package" +// @Param maximumVersion query string true "Set to 1 to only return the highest version for each package name" +// @Success 200 {object} string "msg" +// @Failure 404 {object} Error "Not Found" +// @Failure 404 {object} Error "Internal Server Error" +// @Router /api/repos/{name}/packages [get] func apiReposPackagesShow(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.LocalRepoCollection() repo, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } err = collection.LoadComplete(repo) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } showPackages(c, repo.RefList(), collectionFactory) } +type reposPackagesAddDeleteParams struct { + // Package Refs + PackageRefs []string `binding:"required" json:"PackageRefs" example:""` +} + // Handler for both add and delete func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error) { - var b struct { - PackageRefs []string - } + var b reposPackagesAddDeleteParams if c.Bind(&b) != nil { return @@ -193,18 +353,18 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li repo, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) - return - } - - err = collection.LoadComplete(repo) - if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 404, err) return } resources := []string{string(repo.Key())} - maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + + maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.LoadComplete(repo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + out.Printf("Loading packages...\n") list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil) if err != nil { @@ -239,7 +399,21 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li }) } -// POST /repos/:name/packages +// @Summary Add Packages by Key +// @Description **Add packages to local repository by package keys.** +// @Description +// @Description Any package can be added that is present in the aptly database (from any mirror, snapshot, local repository). This API combined with package list (search) APIs allows one to implement importing, copying, moving packages around. +// @Description +// @Description API verifies that packages actually exist in aptly database and checks constraint that conflicting packages can’t be part of the same local repository. +// @Tags Repos +// @Produce json +// @Param request body reposPackagesAddDeleteParams true "Parameters" +// @Param _async query bool false "Run in background and return task object" +// @Success 200 {object} string "msg" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Failure 400 {object} Error "Internal Server Error" +// @Router /api/repos/{name}/packages [post] func apiReposPackagesAdd(c *gin.Context) { apiReposPackagesAddDelete(c, "Add packages to repo ", func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error { out.Printf("Adding package %s\n", p.Name) @@ -247,7 +421,19 @@ func apiReposPackagesAdd(c *gin.Context) { }) } -// DELETE /repos/:name/packages +// @Summary Delete Packages by Key +// @Description **Remove packages from local repository by package keys.** +// @Description +// @Description Any package(s) can be removed from a local repository. Package references from a local repository can be retrieved with GET /api/repos/:name/packages. +// @Tags Repos +// @Produce json +// @Param request body reposPackagesAddDeleteParams true "Parameters" +// @Param _async query bool false "Run in background and return task object" +// @Success 200 {object} string "msg" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Failure 400 {object} Error "Internal Server Error" +// @Router /api/repos/{name}/packages [delete] func apiReposPackagesDelete(c *gin.Context) { apiReposPackagesAddDelete(c, "Delete packages from repo ", func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error { out.Printf("Removing package %s\n", p.Name) @@ -256,13 +442,43 @@ func apiReposPackagesDelete(c *gin.Context) { }) } -// POST /repos/:name/file/:dir/:file +// @Summary Add Uploaded File +// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically. +// @Description Adding same package to local repository is not an error. +// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import). +// @Tags Repos +// @Param name path string true "Repository name" +// @Param dir path string true "Directory of packages" +// @Param file path string false "Filename (optional)" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 {string} string "OK" +// @Failure 400 {object} Error "wrong file" +// @Failure 404 {object} Error "Repository not found" +// @Failure 500 {object} Error "Error adding files" +// @Router /api/repos/{name}/file/{dir}/{file} [post] func apiReposPackageFromFile(c *gin.Context) { // redirect all work to dir method apiReposPackageFromDir(c) } -// POST /repos/:name/file/:dir +// @Summary Add Uploaded Directory +// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically. +// @Description Adding same package to local repository is not an error. +// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import). +// @Tags Repos +// @Param name path string true "Repository name" +// @Param dir path string true "Directory to add" +// @Consume json +// @Param noRemove query string false "when value is set to 1, don’t remove any files" +// @Param forceReplace query string false "when value is set to 1, remove packages conflicting with package being added (in local repository)" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 {string} string "OK" +// @Failure 400 {object} Error "wrong file" +// @Failure 404 {object} Error "Repository not found" +// @Failure 500 {object} Error "Error adding files" +// @Router /api/repos/{name}/file/{dir} [post] func apiReposPackageFromDir(c *gin.Context) { forceReplace := c.Request.URL.Query().Get("forceReplace") == "1" noRemove := c.Request.URL.Query().Get("noRemove") == "1" @@ -271,10 +487,10 @@ func apiReposPackageFromDir(c *gin.Context) { return } - dirParam := c.Params.ByName("dir") - fileParam := c.Params.ByName("file") + dirParam := utils.SanitizePath(c.Params.ByName("dir")) + fileParam := utils.SanitizePath(c.Params.ByName("file")) if fileParam != "" && !verifyPath(fileParam) { - c.AbortWithError(400, fmt.Errorf("wrong file")) + AbortWithJSONError(c, 400, fmt.Errorf("wrong file")) return } @@ -284,13 +500,7 @@ func apiReposPackageFromDir(c *gin.Context) { name := c.Params.ByName("name") repo, err := collection.ByName(name) if err != nil { - c.AbortWithError(404, err) - return - } - - err = collection.LoadComplete(repo) - if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 404, err) return } @@ -306,7 +516,12 @@ func apiReposPackageFromDir(c *gin.Context) { resources := []string{string(repo.Key())} resources = append(resources, sources...) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.LoadComplete(repo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + verifier := context.GetVerifier() var ( @@ -382,13 +597,208 @@ func apiReposPackageFromDir(c *gin.Context) { }) } -// POST /repos/:name/include/:dir/:file +type reposCopyPackageParams struct { + // Copy also dependencies + WithDeps bool `json:"with-deps,omitempty"` + // Do not perform operations + DryRun bool `json:"dry-run,omitempty"` +} + +// @Summary Copy Package +// @Description Copies a package from a source to destination repository +// @Tags Repos +// @Produce json +// @Param name path string true "Source repo" +// @Param src path string true "Destination repo" +// @Param file path string true "File/packages to copy" +// @Param _async query bool false "Run in background and return task object" +// @Success 200 {object} task.ProcessReturnValue "msg" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Failure 422 {object} Error "Unprocessable Entity" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/repos/{name}/copy/{src}/{file} [post] +func apiReposCopyPackage(c *gin.Context) { + dstRepoName := c.Params.ByName("name") + srcRepoName := c.Params.ByName("src") + fileName := c.Params.ByName("file") + + jsonBody := reposCopyPackageParams{ + WithDeps: false, + DryRun: false, + } + + err := c.Bind(&jsonBody) + if err != nil { + return + } + + collectionFactory := context.NewCollectionFactory() + dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName) + if err != nil { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err)) + return + } + + var srcRepo *deb.LocalRepo + srcRepo, err = collectionFactory.LocalRepoCollection().ByName(srcRepoName) + if err != nil { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err)) + return + } + + if srcRepo.UUID == dstRepo.UUID { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest and source are identical")) + return + } + + taskName := fmt.Sprintf("Copy packages from repo %s to repo %s", srcRepoName, dstRepoName) + resources := []string{string(dstRepo.Key()), string(srcRepo.Key())} + + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err) + } + + err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err) + } + + srcRefList := srcRepo.RefList() + + reporter := &aptly.RecordingResultReporter{ + Warnings: []string{}, + AddedLines: []string{}, + RemovedLines: []string{}, + } + + dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.PackageCollection(), context.Progress()) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err) + } + + srcList, err := deb.NewPackageListFromRefList(srcRefList, collectionFactory.PackageCollection(), context.Progress()) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err) + } + + srcList.PrepareIndex() + + var architecturesList []string + + if jsonBody.WithDeps { + dstList.PrepareIndex() + + // Calculate architectures + if len(context.ArchitecturesList()) > 0 { + architecturesList = context.ArchitecturesList() + } else { + architecturesList = dstList.Architectures(false) + } + + sort.Strings(architecturesList) + + if len(architecturesList) == 0 { + return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to determine list of architectures, please specify explicitly") + } + } + + // srcList.Filter|FilterWithProgress only accept query list + queries := make([]deb.PackageQuery, 1) + queries[0], err = query.Parse(fileName) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to parse query '%s': %s", fileName, err) + } + + toProcess, err := srcList.Filter(deb.FilterOptions{ + Queries: queries, + WithDependencies: jsonBody.WithDeps, + Source: dstList, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + Progress: context.Progress(), + }) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("filter error: %s", err) + } + + if toProcess.Len() == 0 { + return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("no package found for filter: '%s'", fileName) + } + + err = toProcess.ForEach(func(p *deb.Package) error { + err = dstList.Add(p) + if err != nil { + return err + } + + name := fmt.Sprintf("added %s-%s(%s)", p.Name, p.Version, p.Architecture) + reporter.AddedLines = append(reporter.AddedLines, name) + return nil + }) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("error processing dest add: %s", err) + } + + if jsonBody.DryRun { + reporter.Warning("Changes not saved, as dry run has been requested") + } else { + dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList)) + + err = collectionFactory.LocalRepoCollection().Update(dstRepo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err) + } + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{ + "Report": reporter, + }}, nil + }) +} + +// @Summary Include File from Directory +// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api. +// @Tags Repos +// @Produce json +// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package" +// @Param noRemoveFiles query int false "when value is set to 1, don’t remove files that have been imported successfully into repository" +// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files" +// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature" +// @Param _async query bool false "Run in background and return task object" +// @Success 200 {object} string "msg" +// @Failure 404 {object} Error "Not Found" +// @Router /api/repos/{name}/include/{dir}/{file} [post] func apiReposIncludePackageFromFile(c *gin.Context) { // redirect all work to dir method apiReposIncludePackageFromDir(c) } -// POST /repos/:name/include/:dir +type reposIncludePackageFromDirReport struct { + Warnings []string + Added []string + Deleted []string +} + +type reposIncludePackageFromDirResponse struct { + Report reposIncludePackageFromDirReport + FailedFiles []string +} + +// @Summary Include Directory +// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api. +// @Tags Repos +// @Produce json +// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package" +// @Param noRemoveFiles query int false "when value is set to 1, don’t remove files that have been imported successfully into repository" +// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files" +// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature" +// @Param _async query bool false "Run in background and return task object" +// @Success 200 {object} reposIncludePackageFromDirResponse "Response" +// @Failure 404 {object} Error "Not Found" +// @Router /api/repos/{name}/include/{dir} [post] func apiReposIncludePackageFromDir(c *gin.Context) { forceReplace := c.Request.URL.Query().Get("forceReplace") == "1" noRemoveFiles := c.Request.URL.Query().Get("noRemoveFiles") == "1" @@ -404,10 +814,10 @@ func apiReposIncludePackageFromDir(c *gin.Context) { var sources []string var taskName string - dirParam := c.Params.ByName("dir") - fileParam := c.Params.ByName("file") + dirParam := utils.SanitizePath(c.Params.ByName("dir")) + fileParam := utils.SanitizePath(c.Params.ByName("file")) if fileParam != "" && !verifyPath(fileParam) { - c.AbortWithError(400, fmt.Errorf("wrong file")) + AbortWithJSONError(c, 400, fmt.Errorf("wrong file")) return } @@ -421,7 +831,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) { repoTemplate, err := template.New("repo").Parse(repoTemplateString) if err != nil { - c.AbortWithError(400, fmt.Errorf("error parsing repo template: %s", err)) + AbortWithJSONError(c, 400, fmt.Errorf("error parsing repo template: %s", err)) return } @@ -432,7 +842,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) { // repo template string is simple text so only use resource key of specific repository repo, err := collectionFactory.LocalRepoCollection().ByName(repoTemplateString) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } @@ -440,7 +850,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) { } resources = append(resources, sources...) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { var ( err error verifier = context.GetVerifier() @@ -490,6 +900,5 @@ func apiReposIncludePackageFromDir(c *gin.Context) { "Report": reporter, "FailedFiles": failedFiles, }}, nil - }) } diff --git a/api/router.go b/api/router.go index d3b4e98f..3536ab09 100644 --- a/api/router.go +++ b/api/router.go @@ -2,36 +2,91 @@ package api import ( "net/http" + "os" + "sync/atomic" + "github.com/aptly-dev/aptly/aptly" ctx "github.com/aptly-dev/aptly/context" + "github.com/aptly-dev/aptly/utils" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/zerolog/log" + + "github.com/aptly-dev/aptly/docs" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" ) var context *ctx.AptlyContext func apiMetricsGet() gin.HandlerFunc { return func(c *gin.Context) { + countPackagesByRepos() promhttp.Handler().ServeHTTP(c.Writer, c.Request) } } +func redirectSwagger(c *gin.Context) { + if c.Request.URL.Path == "/docs/index.html" { + c.Redirect(http.StatusMovedPermanently, "/docs.html") + return + } + if c.Request.URL.Path == "/docs/" { + c.Redirect(http.StatusMovedPermanently, "/docs.html") + return + } + if c.Request.URL.Path == "/docs" { + c.Redirect(http.StatusMovedPermanently, "/docs.html") + return + } + c.Next() +} + // Router returns prebuilt with routes http.Handler func Router(c *ctx.AptlyContext) http.Handler { - context = c - - router := gin.Default() - router.UseRawPath = true - router.Use(gin.ErrorLogger()) - - if c.Config().EnableMetricsEndpoint { - router.Use(instrumentHandlerInFlight(apiRequestsInFlightGauge, getBasePath)) - router.Use(instrumentHandlerCounter(apiRequestsTotalCounter, getBasePath)) - router.Use(instrumentHandlerRequestSize(apiRequestSizeSummary, getBasePath)) - router.Use(instrumentHandlerResponseSize(apiResponseSizeSummary, getBasePath)) - router.Use(instrumentHandlerDuration(apiRequestsDurationSummary, getBasePath)) + if aptly.EnableDebug { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) } + router := gin.New() + context = c + + router.UseRawPath = true + + if c.Config().LogFormat == "json" { + c.StructuredLogging(true) + utils.SetupJSONLogger(c.Config().LogLevel, os.Stdout) + gin.DefaultWriter = utils.LogWriter{Logger: log.Logger} + router.Use(JSONLogger()) + } else { + c.StructuredLogging(false) + utils.SetupDefaultLogger(c.Config().LogLevel) + router.Use(gin.Logger()) + } + + router.Use(gin.Recovery(), gin.ErrorLogger()) + + if c.Config().EnableSwaggerEndpoint { + router.GET("docs.html", func(c *gin.Context) { + c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML) + }) + router.Use(redirectSwagger) + url := ginSwagger.URL("/docs/doc.json") + router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url)) + } + + if c.Config().EnableMetricsEndpoint { + MetricsCollectorRegistrar.Register(router) + } + + if c.Config().ServeInAPIMode { + router.GET("/repos/", reposListInAPIMode(c.Config().FileSystemPublishRoots)) + router.GET("/repos/:storage/*pkgPath", reposServeInAPIMode) + } + + api := router.Group("/api") if context.Flags().Lookup("no-lock").Value.Get().(bool) { // We use a goroutine to count the number of // concurrent requests. When no more requests are @@ -40,7 +95,7 @@ func Router(c *ctx.AptlyContext) http.Handler { go acquireDatabase() - router.Use(func(c *gin.Context) { + api.Use(func(c *gin.Context) { var err error errCh := make(chan error) @@ -48,7 +103,7 @@ func Router(c *ctx.AptlyContext) http.Handler { err = <-errCh if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } @@ -56,7 +111,7 @@ func Router(c *ctx.AptlyContext) http.Handler { dbRequests <- dbRequest{releasedb, errCh} err = <-errCh if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) } }() @@ -64,99 +119,119 @@ func Router(c *ctx.AptlyContext) http.Handler { }) } - root := router.Group("/api") - { if c.Config().EnableMetricsEndpoint { - root.GET("/metrics", apiMetricsGet()) + api.GET("/metrics", apiMetricsGet()) } - root.GET("/version", apiVersion) + api.GET("/version", apiVersion) + api.GET("/storage", apiDiskFree) + + isReady := &atomic.Value{} + isReady.Store(false) + defer isReady.Store(true) + api.GET("/ready", apiReady(isReady)) + api.GET("/healthy", apiHealthy) } { - root.GET("/repos", apiReposList) - root.POST("/repos", apiReposCreate) - root.GET("/repos/:name", apiReposShow) - root.PUT("/repos/:name", apiReposEdit) - root.DELETE("/repos/:name", apiReposDrop) + api.GET("/repos", apiReposList) + api.POST("/repos", apiReposCreate) + api.GET("/repos/:name", apiReposShow) + api.PUT("/repos/:name", apiReposEdit) + api.DELETE("/repos/:name", apiReposDrop) - root.GET("/repos/:name/packages", apiReposPackagesShow) - root.POST("/repos/:name/packages", apiReposPackagesAdd) - root.DELETE("/repos/:name/packages", apiReposPackagesDelete) + api.GET("/repos/:name/packages", apiReposPackagesShow) + api.POST("/repos/:name/packages", apiReposPackagesAdd) + api.DELETE("/repos/:name/packages", apiReposPackagesDelete) - root.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile) - root.POST("/repos/:name/file/:dir", apiReposPackageFromDir) + api.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile) + api.POST("/repos/:name/file/:dir", apiReposPackageFromDir) + api.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage) - root.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile) - root.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir) + api.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile) + api.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir) - root.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository) + api.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository) } { - root.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror) + api.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror) } { - root.GET("/mirrors", apiMirrorsList) - root.GET("/mirrors/:name", apiMirrorsShow) - root.GET("/mirrors/:name/packages", apiMirrorsPackages) - root.POST("/mirrors", apiMirrorsCreate) - root.PUT("/mirrors/:name", apiMirrorsUpdate) - root.DELETE("/mirrors/:name", apiMirrorsDrop) + api.GET("/mirrors", apiMirrorsList) + api.GET("/mirrors/:name", apiMirrorsShow) + api.GET("/mirrors/:name/packages", apiMirrorsPackages) + api.POST("/mirrors", apiMirrorsCreate) + api.PUT("/mirrors/:name", apiMirrorsUpdate) + api.DELETE("/mirrors/:name", apiMirrorsDrop) } { - root.POST("/gpg/key", apiGPGAddKey) + api.POST("/gpg/key", apiGPGAddKey) } { - root.GET("/files", apiFilesListDirs) - root.POST("/files/:dir", apiFilesUpload) - root.GET("/files/:dir", apiFilesListFiles) - root.DELETE("/files/:dir", apiFilesDeleteDir) - root.DELETE("/files/:dir/:name", apiFilesDeleteFile) + api.GET("/s3", apiS3List) } { - root.GET("/publish", apiPublishList) - root.POST("/publish", apiPublishRepoOrSnapshot) - root.POST("/publish/:prefix", apiPublishRepoOrSnapshot) - root.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch) - root.DELETE("/publish/:prefix/:distribution", apiPublishDrop) + api.GET("/files", apiFilesListDirs) + api.POST("/files/:dir", apiFilesUpload) + api.GET("/files/:dir", apiFilesListFiles) + api.DELETE("/files/:dir", apiFilesDeleteDir) + api.DELETE("/files/:dir/:name", apiFilesDeleteFile) } { - root.GET("/snapshots", apiSnapshotsList) - root.POST("/snapshots", apiSnapshotsCreate) - root.PUT("/snapshots/:name", apiSnapshotsUpdate) - root.GET("/snapshots/:name", apiSnapshotsShow) - root.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages) - root.DELETE("/snapshots/:name", apiSnapshotsDrop) - root.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff) + api.GET("/publish", apiPublishList) + api.GET("/publish/:prefix/:distribution", apiPublishShow) + api.POST("/publish", apiPublishRepoOrSnapshot) + api.POST("/publish/:prefix", apiPublishRepoOrSnapshot) + api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch) + api.DELETE("/publish/:prefix/:distribution", apiPublishDrop) + api.POST("/publish/:prefix/:distribution/sources", apiPublishAddSource) + api.GET("/publish/:prefix/:distribution/sources", apiPublishListChanges) + api.PUT("/publish/:prefix/:distribution/sources", apiPublishSetSources) + api.DELETE("/publish/:prefix/:distribution/sources", apiPublishDropChanges) + api.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishUpdateSource) + api.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishRemoveSource) + api.POST("/publish/:prefix/:distribution/update", apiPublishUpdate) } { - root.GET("/packages/:key", apiPackagesShow) + api.GET("/snapshots", apiSnapshotsList) + api.POST("/snapshots", apiSnapshotsCreate) + api.PUT("/snapshots/:name", apiSnapshotsUpdate) + api.GET("/snapshots/:name", apiSnapshotsShow) + api.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages) + api.DELETE("/snapshots/:name", apiSnapshotsDrop) + api.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff) + api.POST("/snapshots/:name/merge", apiSnapshotsMerge) + api.POST("/snapshots/:name/pull", apiSnapshotsPull) } { - root.GET("/graph.:ext", apiGraph) + api.GET("/packages/:key", apiPackagesShow) + api.GET("/packages", apiPackages) + } + + { + api.GET("/graph.:ext", apiGraph) } { - root.POST("/db/cleanup", apiDbCleanup) + api.POST("/db/cleanup", apiDbCleanup) } { - root.GET("/tasks", apiTasksList) - root.POST("/tasks-clear", apiTasksClear) - root.GET("/tasks-wait", apiTasksWait) - root.GET("/tasks/:id/wait", apiTasksWaitForTaskByID) - root.GET("/tasks/:id/output", apiTasksOutputShow) - root.GET("/tasks/:id/detail", apiTasksDetailShow) - root.GET("/tasks/:id/return_value", apiTasksReturnValueShow) - root.GET("/tasks/:id", apiTasksShow) - root.DELETE("/tasks/:id", apiTasksDelete) - root.POST("/tasks-dummy", apiTasksDummy) + api.GET("/tasks", apiTasksList) + api.POST("/tasks-clear", apiTasksClear) + api.GET("/tasks-wait", apiTasksWait) + api.GET("/tasks/:id/wait", apiTasksWaitForTaskByID) + api.GET("/tasks/:id/output", apiTasksOutputShow) + api.GET("/tasks/:id/detail", apiTasksDetailShow) + api.GET("/tasks/:id/return_value", apiTasksReturnValueShow) + api.GET("/tasks/:id", apiTasksShow) + api.DELETE("/tasks/:id", apiTasksDelete) } return router diff --git a/api/s3.go b/api/s3.go new file mode 100644 index 00000000..f38b0847 --- /dev/null +++ b/api/s3.go @@ -0,0 +1,21 @@ +package api + +import ( + "github.com/gin-gonic/gin" +) + +// @Summary S3 buckets +// @Description **Get list of S3 buckets** +// @Description +// @Description List configured S3 buckets. +// @Tags Status +// @Produce json +// @Success 200 {array} string "List of S3 buckets" +// @Router /api/s3 [get] +func apiS3List(c *gin.Context) { + keys := []string{} + for k := range context.Config().S3PublishRoots { + keys = append(keys, k) + } + c.JSON(200, keys) +} diff --git a/api/snapshot.go b/api/snapshot.go index e4334e6c..6082b09a 100644 --- a/api/snapshot.go +++ b/api/snapshot.go @@ -3,15 +3,25 @@ package api import ( "fmt" "net/http" + "sort" + "strings" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/database" "github.com/aptly-dev/aptly/deb" + "github.com/aptly-dev/aptly/query" "github.com/aptly-dev/aptly/task" "github.com/gin-gonic/gin" ) -// GET /api/snapshots +// @Summary List Snapshots +// @Description **Get list of snapshots** +// @Description +// @Description Each snapshot is returned as in “show” API. +// @Tags Snapshots +// @Produce json +// @Success 200 {array} deb.Snapshot +// @Router /api/snapshots [get] func apiSnapshotsList(c *gin.Context) { SortMethodString := c.Request.URL.Query().Get("sort") @@ -31,19 +41,34 @@ func apiSnapshotsList(c *gin.Context) { c.JSON(200, result) } -// POST /api/mirrors/:name/snapshots/ +type snapshotsCreateFromMirrorParams struct { + // Name of snapshot to create + Name string `binding:"required" json:"Name" example:"snap1"` + // Description of snapshot + Description string ` json:"Description"` +} + +// @Summary Snapshot Mirror +// @Description **Create a snapshot of a mirror** +// @Tags Snapshots +// @Produce json +// @Param request body snapshotsCreateFromMirrorParams true "Parameters" +// @Param name path string true "Mirror name" +// @Param _async query bool false "Run in background and return task object" +// @Success 201 {object} deb.Snapshot "Created Snapshot" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Mirror Not Found" +// @Failure 409 {object} Error "Conflicting snapshot" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/mirrors/{name}/snapshots [post] func apiSnapshotsCreateFromMirror(c *gin.Context) { var ( err error repo *deb.RemoteRepo snapshot *deb.Snapshot + b snapshotsCreateFromMirrorParams ) - var b struct { - Name string `binding:"required"` - Description string - } - if c.Bind(&b) != nil { return } @@ -55,14 +80,14 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) { repo, err = collection.ByName(name) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } // including snapshot resource key resources := []string{string(repo.Key()), "S" + b.Name} taskName := fmt.Sprintf("Create snapshot of mirror %s", name) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { err := repo.CheckLock() if err != nil { return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err @@ -90,20 +115,37 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) { }) } -// POST /api/snapshots +type snapshotsCreateParams struct { + // Name of snapshot to create + Name string `binding:"required" json:"Name" example:"snap2"` + // Description of snapshot + Description string ` json:"Description"` + // List of source snapshots + SourceSnapshots []string ` json:"SourceSnapshots" example:"snap1"` + // List of package refs + PackageRefs []string ` json:"PackageRefs" example:""` +} + +// @Summary Snapshot Packages +// @Description **Create a snapshot from package refs** +// @Description +// @Description Refs can be obtained from snapshots, local repos, or mirrors +// @Tags Snapshots +// @Param request body snapshotsCreateParams true "Parameters" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 201 {object} deb.Snapshot "Created snapshot" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Source snapshot or package refs not found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/snapshots [post] func apiSnapshotsCreate(c *gin.Context) { var ( err error snapshot *deb.Snapshot + b snapshotsCreateParams ) - var b struct { - Name string `binding:"required"` - Description string - SourceSnapshots []string - PackageRefs []string - } - if c.Bind(&b) != nil { return } @@ -123,20 +165,21 @@ func apiSnapshotsCreate(c *gin.Context) { for i := range b.SourceSnapshots { sources[i], err = snapshotCollection.ByName(b.SourceSnapshots[i]) if err != nil { - c.AbortWithError(404, err) - return - } - - err = snapshotCollection.LoadComplete(sources[i]) - if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 404, err) return } resources = append(resources, string(sources[i].ResourceKey())) } - maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + for i := range sources { + err = snapshotCollection.LoadComplete(sources[i]) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + } + list := deb.NewPackageList() // verify package refs and build package list @@ -160,23 +203,39 @@ func apiSnapshotsCreate(c *gin.Context) { if err != nil { return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err } - return &task.ProcessReturnValue{Code: http.StatusCreated, Value: nil}, nil + return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil }) } -// POST /api/repos/:name/snapshots +type snapshotsCreateFromRepositoryParams struct { + // Name of snapshot to create + Name string `binding:"required" json:"Name" example:"snap1"` + // Description of snapshot + Description string ` json:"Description"` +} + +// @Summary Snapshot Repository +// @Description **Create a snapshot of a repository by name** +// @Tags Snapshots +// @Param name path string true "Repository name" +// @Consume json +// @Param request body snapshotsCreateFromRepositoryParams true "Parameters" +// @Param name path string true "Name of the snapshot" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 201 {object} deb.Snapshot "Created snapshot object" +// @Failure 400 {object} Error "Bad Request" +// @Failure 500 {object} Error "Internal Server Error" +// @Failure 404 {object} Error "Repo Not Found" +// @Router /api/repos/{name}/snapshots [post] func apiSnapshotsCreateFromRepository(c *gin.Context) { var ( err error repo *deb.LocalRepo snapshot *deb.Snapshot + b snapshotsCreateFromRepositoryParams ) - var b struct { - Name string `binding:"required"` - Description string - } - if c.Bind(&b) != nil { return } @@ -188,14 +247,14 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) { repo, err = collection.ByName(name) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } // including snapshot resource key resources := []string{string(repo.Key()), "S" + b.Name} taskName := fmt.Sprintf("Create snapshot of repo %s", name) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { err := collection.LoadComplete(repo) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err @@ -218,18 +277,32 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) { }) } -// PUT /api/snapshots/:name +type snapshotsUpdateParams struct { + // Change Name of snapshot + Name string ` json:"Name" example:"snap2"` + // Change Description of snapshot + Description string `json:"Description"` +} + +// @Summary Update Snapshot +// @Description **Update snapshot metadata (Name, Description)** +// @Tags Snapshots +// @Param request body snapshotsUpdateParams true "Parameters" +// @Param name path string true "Snapshot name" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 {object} deb.Snapshot "Updated snapshot object" +// @Failure 404 {object} Error "Snapshot Not Found" +// @Failure 409 {object} Error "Conflicting snapshot" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/snapshots/{name} [put] func apiSnapshotsUpdate(c *gin.Context) { var ( err error snapshot *deb.Snapshot + b snapshotsUpdateParams ) - var b struct { - Name string - Description string - } - if c.Bind(&b) != nil { return } @@ -240,13 +313,13 @@ func apiSnapshotsUpdate(c *gin.Context) { snapshot, err = collection.ByName(name) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } resources := []string{string(snapshot.ResourceKey()), "S" + b.Name} taskName := fmt.Sprintf("Update snapshot %s", name) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { _, err := collection.ByName(b.Name) if err == nil { return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name) @@ -268,27 +341,48 @@ func apiSnapshotsUpdate(c *gin.Context) { }) } -// GET /api/snapshots/:name +// @Summary Get Snapshot Info +// @Description **Query detailed information about a snapshot by name** +// @Tags Snapshots +// @Param name path string true "Name of the snapshot" +// @Produce json +// @Success 200 {object} deb.Snapshot "msg" +// @Failure 404 {object} Error "Snapshot Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/snapshots/{name} [get] func apiSnapshotsShow(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.SnapshotCollection() snapshot, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } err = collection.LoadComplete(snapshot) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } c.JSON(200, snapshot) } -// DELETE /api/snapshots/:name +// @Summary Delete Snapshot +// @Description **Delete snapshot by name** +// @Description Cannot drop snapshots that are published. +// @Description Needs force=1 to drop snapshots used as source by other snapshots. +// @Tags Snapshots +// @Param name path string true "Snapshot name" +// @Param force query string false "Force operation" +// @Param _async query bool false "Run in background and return task object" +// @Produce json +// @Success 200 "" +// @Failure 404 {object} Error "Snapshot Not Found" +// @Failure 409 {object} Error "Snapshot in use" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/snapshots/{name} [delete] func apiSnapshotsDrop(c *gin.Context) { name := c.Params.ByName("name") force := c.Request.URL.Query().Get("force") == "1" @@ -299,13 +393,13 @@ func apiSnapshotsDrop(c *gin.Context) { snapshot, err := snapshotCollection.ByName(name) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } resources := []string{string(snapshot.ResourceKey())} taskName := fmt.Sprintf("Delete snapshot %s", name) - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { published := publishedCollection.BySnapshot(snapshot) if len(published) > 0 { @@ -327,7 +421,19 @@ func apiSnapshotsDrop(c *gin.Context) { }) } -// GET /api/snapshots/:name/diff/:withSnapshot +// @Summary Snapshot diff +// @Description **Return the diff between two snapshots (name & withSnapshot)** +// @Description Provide `onlyMatching=1` to return only packages present in both snapshots. +// @Description Otherwise, returns a `left` and `right` result providing packages only in the first and second snapshots +// @Tags Snapshots +// @Produce json +// @Param name path string true "Snapshot name" +// @Param withSnapshot path string true "Snapshot name to diff against" +// @Param onlyMatching query string false "Only return packages present in both snapshots" +// @Success 200 {array} deb.PackageDiff "Package Diff" +// @Failure 404 {object} Error "Snapshot Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/snapshots/{name}/diff/{withSnapshot} [get] func apiSnapshotsDiff(c *gin.Context) { onlyMatching := c.Request.URL.Query().Get("onlyMatching") == "1" @@ -336,32 +442,32 @@ func apiSnapshotsDiff(c *gin.Context) { snapshotA, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } err = collection.LoadComplete(snapshotA) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } err = collection.LoadComplete(snapshotB) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } // Calculate diff diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), collectionFactory.PackageCollection()) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } @@ -378,22 +484,334 @@ func apiSnapshotsDiff(c *gin.Context) { c.JSON(200, result) } -// GET /api/snapshots/:name/packages +// @Summary List Snapshot Packages +// @Description **List all packages in snapshot or perform search on snapshot contents and return results** +// @Description If `q` query parameter is missing, return all packages, otherwise return packages that match q +// @Tags Snapshots +// @Produce json +// @Param name path string true "Snapshot to search" +// @Param q query string false "Package query (e.g Name%20(~%20matlab))" +// @Param withDeps query string false "Set to 1 to include dependencies when evaluating package query" +// @Param format query string false "Set to 'details' to return extra info about each package" +// @Param maximumVersion query string false "Set to 1 to only return the highest version for each package name" +// @Success 200 {array} string "Package info" +// @Failure 404 {object} Error "Snapshot Not Found" +// @Failure 500 {object} Error "Internal Server Error" +// @Router /api/snapshots/{name}/packages [get] func apiSnapshotsSearchPackages(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.SnapshotCollection() snapshot, err := collection.ByName(c.Params.ByName("name")) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } err = collection.LoadComplete(snapshot) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } showPackages(c, snapshot.RefList(), collectionFactory) } + +type snapshotsMergeParams struct { + // List of snapshot names to be merged + Sources []string `binding:"required" json:"Sources" example:"snapshot1"` +} + +// @Summary Snapshot Merge +// @Description **Merge several source snapshots into a new snapshot** +// @Description +// @Description Merge happens from left to right. By default, packages with the same name-architecture pair are replaced during merge (package from latest snapshot on the list wins). +// @Description +// @Description If only one snapshot is specified, merge copies source into destination. +// @Tags Snapshots +// @Consume json +// @Produce json +// @Param name path string true "Name of the snapshot to be created" +// @Param latest query int false "merge only the latest version of each package" +// @Param no-remove query int false "all versions of packages are preserved during merge" +// @Param request body snapshotsMergeParams true "Parameters" +// @Param _async query bool false "Run in background and return task object" +// @Success 201 {object} deb.Snapshot "Resulting snapshot object" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/snapshots/{name}/merge [post] +func apiSnapshotsMerge(c *gin.Context) { + var ( + err error + snapshot *deb.Snapshot + body snapshotsMergeParams + ) + + name := c.Params.ByName("name") + + if c.Bind(&body) != nil { + return + } + + if len(body.Sources) < 1 { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("At least one source snapshot is required")) + return + } + + latest := c.Request.URL.Query().Get("latest") == "1" + noRemove := c.Request.URL.Query().Get("no-remove") == "1" + overrideMatching := !latest && !noRemove + + if noRemove && latest { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("no-remove and latest are mutually exclusive")) + return + } + + collectionFactory := context.NewCollectionFactory() + snapshotCollection := collectionFactory.SnapshotCollection() + + sources := make([]*deb.Snapshot, len(body.Sources)) + resources := make([]string, len(sources)) + for i := range body.Sources { + sources[i], err = snapshotCollection.ByName(body.Sources[i]) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, err) + return + } + + resources[i] = string(sources[i].ResourceKey()) + } + + maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = snapshotCollection.LoadComplete(sources[0]) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + result := sources[0].RefList() + for i := 1; i < len(sources); i++ { + err = snapshotCollection.LoadComplete(sources[i]) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + result = result.Merge(sources[i].RefList(), overrideMatching, false) + } + + if latest { + result.FilterLatestRefs() + } + + sourceDescription := make([]string, len(sources)) + for i, s := range sources { + sourceDescription[i] = fmt.Sprintf("'%s'", s.Name) + } + + snapshot = deb.NewSnapshotFromRefList(name, sources, result, + fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", "))) + + err = collectionFactory.SnapshotCollection().Add(snapshot) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil + }) +} + +type snapshotsPullParams struct { + // Source name to be searched for packages and dependencies + Source string `binding:"required" json:"Source" example:"source-snapshot"` + // Name of the snapshot to be created + Destination string `binding:"required" json:"Destination" example:"idestination-snapshot"` + // List of package queries (i.e. name of package to be pulled from `Source`) + Queries []string `binding:"required" json:"Queries" example:"xserver-xorg"` + // List of architectures (optional) + Architectures []string ` json:"Architectures" example:"amd64, armhf"` +} + +// @Summary Snapshot Pull +// @Description **Pulls new packages and dependencies from a source snapshot into a new snapshot** +// @Description +// @Description May also upgrade package versions if name snapshot already contains packages being pulled. New snapshot `Destination` is created as result of this process. +// @Description If architectures are limited (with config architectures or parameter `Architectures`, only mentioned architectures are processed, otherwise aptly will process all architectures in the snapshot. +// @Description If following dependencies by source is enabled (using dependencyFollowSource config), pulling binary packages would also pull corresponding source packages as well. +// @Description By default aptly would remove packages matching name and architecture while importing: e.g. when importing software_1.3_amd64, package software_1.2.9_amd64 would be removed. +// @Description +// @Description With flag `no-remove` both package versions would stay in the snapshot. +// @Description +// @Description Aptly pulls first package matching each of package queries, but with flag -all-matches all matching packages would be pulled. +// @Tags Snapshots +// @Param request body snapshotsPullParams true "Parameters" +// @Param name path string true "Name of the snapshot to be created" +// @Param all-matches query int false "pull all the packages that satisfy the dependency version requirements (default is to pull first matching package): 1 to enable" +// @Param dry-run query int false "don’t create destination snapshot, just show what would be pulled: 1 to enable" +// @Param no-deps query int false "don’t process dependencies, just pull listed packages: 1 to enable" +// @Param no-remove query int false "don’t remove other package versions when pulling package: 1 to enable" +// @Param _async query bool false "Run in background and return task object" +// @Consume json +// @Produce json +// @Success 200 {object} deb.Snapshot "Resulting Snapshot object" +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Not Found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/snapshots/{name}/pull [post] +func apiSnapshotsPull(c *gin.Context) { + var ( + err error + destinationSnapshot *deb.Snapshot + body snapshotsPullParams + ) + + name := c.Params.ByName("name") + + if err = c.BindJSON(&body); err != nil { + AbortWithJSONError(c, http.StatusBadRequest, err) + return + } + + allMatches := c.Request.URL.Query().Get("all-matches") == "1" + dryRun := c.Request.URL.Query().Get("dry-run") == "1" + noDeps := c.Request.URL.Query().Get("no-deps") == "1" + noRemove := c.Request.URL.Query().Get("no-remove") == "1" + + collectionFactory := context.NewCollectionFactory() + + // Load snapshot + toSnapshot, err := collectionFactory.SnapshotCollection().ByName(name) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, err) + return + } + + // Load snapshot + sourceSnapshot, err := collectionFactory.SnapshotCollection().ByName(body.Source) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, err) + return + } + + resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())} + taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination) + maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + + // convert snapshots to package list + toPackageList, err := deb.NewPackageListFromRefList(toSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress()) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + sourcePackageList, err := deb.NewPackageListFromRefList(sourceSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress()) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + + toPackageList.PrepareIndex() + sourcePackageList.PrepareIndex() + + var architecturesList []string + + if len(context.ArchitecturesList()) > 0 { + architecturesList = context.ArchitecturesList() + } else { + architecturesList = toPackageList.Architectures(false) + } + + architecturesList = append(architecturesList, body.Architectures...) + sort.Strings(architecturesList) + + if len(architecturesList) == 0 { + err := fmt.Errorf("unable to determine list of architectures, please specify explicitly") + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + + // Build architecture query: (arch == "i386" | arch == "amd64" | ...) + var archQuery deb.PackageQuery = &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: ""} + for _, arch := range architecturesList { + archQuery = &deb.OrQuery{L: &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: arch}, R: archQuery} + } + + queries := make([]deb.PackageQuery, len(body.Queries)) + for i, q := range body.Queries { + queries[i], err = query.Parse(q) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + // Add architecture filter + queries[i] = &deb.AndQuery{L: queries[i], R: archQuery} + } + + // Filter with dependencies as requested + destinationPackageList, err := sourcePackageList.Filter(deb.FilterOptions{ + Queries: queries, + WithDependencies: !noDeps, + Source: toPackageList, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + Progress: context.Progress(), + }) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + destinationPackageList.PrepareIndex() + + removedPackages := []string{} + addedPackages := []string{} + alreadySeen := map[string]bool{} + + destinationPackageList.ForEachIndexed(func(pkg *deb.Package) error { + key := pkg.Architecture + "_" + pkg.Name + _, seen := alreadySeen[key] + + // If we haven't seen such name-architecture pair and were instructed to remove, remove it + if !noRemove && !seen { + // 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 { + toPackageList.Remove(p) + removedPackages = append(removedPackages, p.String()) + } + } + + // If !allMatches, add only first matching name-arch package + if !seen || allMatches { + toPackageList.Add(pkg) + addedPackages = append(addedPackages, pkg.String()) + } + + alreadySeen[key] = true + + return nil + }) + alreadySeen = nil + + if dryRun { + response := struct { + AddedPackages []string `json:"added_packages"` + RemovedPackages []string `json:"removed_packages"` + }{ + AddedPackages: addedPackages, + RemovedPackages: removedPackages, + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: response}, nil + } + + // Create snapshot + destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{toSnapshot, sourceSnapshot}, toPackageList, + fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", toSnapshot.Name, sourceSnapshot.Name, strings.Join(body.Queries, ", "))) + + err = collectionFactory.SnapshotCollection().Add(destinationSnapshot) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + + return &task.ProcessReturnValue{Code: http.StatusCreated, Value: destinationSnapshot}, nil + }) +} diff --git a/api/storage.go b/api/storage.go new file mode 100644 index 00000000..c4f478b8 --- /dev/null +++ b/api/storage.go @@ -0,0 +1,45 @@ +package api + +import ( + "fmt" + "syscall" + + "github.com/gin-gonic/gin" +) + +type diskFree struct { + // Storage size [MiB] + Total uint64 + // Available Storage [MiB] + Free uint64 + // Percentage Full + PercentFull float32 +} + +// @Summary Get Storage Utilization +// @Description **Get disk free information of aptly storage** +// @Description +// @Description Units in MiB. +// @Tags Status +// @Produce json +// @Success 200 {object} diskFree "Storage information" +// @Failure 400 {object} Error "Internal Error" +// @Router /api/storage [get] +func apiDiskFree(c *gin.Context) { + var df diskFree + + fs := context.Config().GetRootDir() + + var stat syscall.Statfs_t + err := syscall.Statfs(fs, &stat) + if err != nil { + AbortWithJSONError(c, 400, fmt.Errorf("Error getting storage info on %s: %s", fs, err)) + return + } + + df.Total = uint64(stat.Blocks) * uint64(stat.Bsize) / 1048576 + df.Free = uint64(stat.Bavail) * uint64(stat.Bsize) / 1048576 + df.PercentFull = 100.0 - float32(stat.Bavail)/float32(stat.Blocks)*100.0 + + c.JSON(200, df) +} diff --git a/api/task.go b/api/task.go index 19cb0614..1339f96a 100644 --- a/api/task.go +++ b/api/task.go @@ -1,155 +1,203 @@ package api import ( - "fmt" - "net/http" "strconv" - "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/task" "github.com/gin-gonic/gin" ) -// GET /tasks +// @Summary List Tasks +// @Description **Get list of available tasks. Each task is returned as in “show” API** +// @Tags Tasks +// @Produce json +// @Success 200 {array} task.Task +// @Router /api/tasks [get] func apiTasksList(c *gin.Context) { list := context.TaskList() c.JSON(200, list.GetTasks()) } -// POST /tasks-clear +// @Summary Clear Tasks +// @Description **Removes finished and failed tasks from internal task list** +// @Tags Tasks +// @Produce json +// @Success 200 "" +// @Router /api/tasks-clear [post] func apiTasksClear(c *gin.Context) { list := context.TaskList() list.Clear() c.JSON(200, gin.H{}) } -// GET /tasks-wait +// @Summary Wait for all Tasks +// @Description **Waits for and returns when all running tasks are complete** +// @Tags Tasks +// @Produce json +// @Success 200 "" +// @Router /api/tasks-wait [get] func apiTasksWait(c *gin.Context) { list := context.TaskList() list.Wait() c.JSON(200, gin.H{}) } -// GET /tasks/:id/wait +// @Summary Wait for Task +// @Description **Waits for and returns when given Task ID is complete** +// @Tags Tasks +// @Produce json +// @Param id path int true "Task ID" +// @Success 200 {object} task.Task +// @Failure 500 {object} Error "invalid syntax, bad id?" +// @Failure 400 {object} Error "Task Not Found" +// @Router /api/tasks/{id}/wait [get] func apiTasksWaitForTaskByID(c *gin.Context) { list := context.TaskList() id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } task, err := list.WaitForTaskByID(int(id)) if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } c.JSON(200, task) } -// GET /tasks/:id +// @Summary Get Task Info +// @Description **Return task information for a given ID** +// @Tags Tasks +// @Produce plain +// @Param id path int true "Task ID" +// @Success 200 {object} task.Task +// @Failure 500 {object} Error "invalid syntax, bad id?" +// @Failure 404 {object} Error "Task Not Found" +// @Router /api/tasks/{id} [get] func apiTasksShow(c *gin.Context) { list := context.TaskList() id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } var task task.Task task, err = list.GetTaskByID(int(id)) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } c.JSON(200, task) } -// GET /tasks/:id/output +// @Summary Get Task Output +// @Description **Return task output for a given ID** +// @Tags Tasks +// @Produce plain +// @Param id path int true "Task ID" +// @Success 200 {object} string "Task output" +// @Failure 500 {object} Error "invalid syntax, bad ID?" +// @Failure 404 {object} Error "Task Not Found" +// @Router /api/tasks/{id}/output [get] func apiTasksOutputShow(c *gin.Context) { list := context.TaskList() id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } var output string output, err = list.GetTaskOutputByID(int(id)) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } c.JSON(200, output) } -// GET /tasks/:id/detail +// @Summary Get Task Details +// @Description **Return task detail for a given ID** +// @Tags Tasks +// @Produce json +// @Param id path int true "Task ID" +// @Success 200 {object} string "Task detail" +// @Failure 500 {object} Error "invalid syntax, bad ID?" +// @Failure 404 {object} Error "Task Not Found" +// @Router /api/tasks/{id}/detail [get] func apiTasksDetailShow(c *gin.Context) { list := context.TaskList() id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } var detail interface{} detail, err = list.GetTaskDetailByID(int(id)) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } c.JSON(200, detail) } -// GET /tasks/:id/return_value +// @Summary Get Task Return Value +// @Description **Return task return value (status code) by given ID** +// @Tags Tasks +// @Produce plain +// @Param id path int true "Task ID" +// @Success 200 {object} string "msg" +// @Failure 500 {object} Error "invalid syntax, bad ID?" +// @Failure 404 {object} Error "Not Found" +// @Router /api/tasks/{id}/return_value [get] func apiTasksReturnValueShow(c *gin.Context) { list := context.TaskList() id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } output, err := list.GetTaskReturnValueByID(int(id)) if err != nil { - c.AbortWithError(404, err) + AbortWithJSONError(c, 404, err) return } c.JSON(200, output) } -// DELETE /tasks/:id +// @Summary Delete Task +// @Description **Delete completed task by given ID. Does not stop task execution** +// @Tags Tasks +// @Produce json +// @Param id path int true "Task ID" +// @Success 200 {object} task.Task +// @Failure 500 {object} Error "invalid syntax, bad ID?" +// @Failure 400 {object} Error "Task in progress or not found" +// @Router /api/tasks/{id} [delete] func apiTasksDelete(c *gin.Context) { list := context.TaskList() id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0) if err != nil { - c.AbortWithError(500, err) + AbortWithJSONError(c, 500, err) return } var delTask task.Task delTask, err = list.DeleteTaskByID(int(id)) if err != nil { - c.AbortWithError(400, err) + AbortWithJSONError(c, 400, err) return } c.JSON(200, delTask) } - -// POST /tasks-dummy -func apiTasksDummy(c *gin.Context) { - resources := []string{"dummy"} - taskName := fmt.Sprintf("Dummy task") - maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { - out.Printf("Dummy task started\n") - detail.Store([]int{1, 2, 3}) - out.Printf("Dummy task finished\n") - return &task.ProcessReturnValue{Code: http.StatusTeapot, Value: []int{1, 2, 3}}, nil - }) -} diff --git a/api/task_test.go b/api/task_test.go deleted file mode 100644 index 8cf05c34..00000000 --- a/api/task_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/aptly-dev/aptly/task" - - . "gopkg.in/check.v1" -) - -type TaskSuite struct { - ApiSuite -} - -var _ = Suite(&TaskSuite{}) - -func (s *TaskSuite) TestTasksDummy(c *C) { - response, _ := s.HTTPRequest("POST", "/api/tasks-dummy", nil) - c.Check(response.Code, Equals, 418) - c.Check(response.Body.String(), Equals, "[1,2,3]") -} - -func (s *TaskSuite) TestTasksDummyAsync(c *C) { - response, _ := s.HTTPRequest("POST", "/api/tasks-dummy?_async=true", nil) - c.Check(response.Code, Equals, 202) - var t task.Task - err := json.Unmarshal(response.Body.Bytes(), &t) - c.Assert(err, IsNil) - c.Check(t.Name, Equals, "Dummy task") - response, _ = s.HTTPRequest("GET", fmt.Sprintf("/api/tasks/%d/wait", t.ID), nil) - err = json.Unmarshal(response.Body.Bytes(), &t) - c.Assert(err, IsNil) - c.Check(t.State, Equals, task.SUCCEEDED) - response, _ = s.HTTPRequest("GET", fmt.Sprintf("/api/tasks/%d/detail", t.ID), nil) - c.Check(response.Code, Equals, 200) - c.Check(response.Body.String(), Equals, "[1,2,3]") - response, _ = s.HTTPRequest("GET", fmt.Sprintf("/api/tasks/%d/output", t.ID), nil) - c.Check(response.Code, Equals, 200) - c.Check(response.Body.String(), Matches, "\"Dummy task started.*") -} - -func (s *TaskSuite) TestTaskDelete(c *C) { - response, _ := s.HTTPRequest("POST", "/api/tasks-dummy?_async=true", nil) - c.Check(response.Code, Equals, 202) - c.Check(response.Body.String(), Equals, "{\"Name\":\"Dummy task\",\"ID\":1,\"State\":0}") - // Give the task time to start - time.Sleep(time.Second) - response, _ = s.HTTPRequest("DELETE", "/api/tasks/1", nil) - c.Check(response.Code, Equals, 200) -} - -func (s *TaskSuite) TestTasksClear(c *C) { - response, _ := s.HTTPRequest("POST", "/api/tasks-dummy?_async=true", nil) - c.Check(response.Code, Equals, 202) - var t task.Task - err := json.Unmarshal(response.Body.Bytes(), &t) - c.Assert(err, IsNil) - c.Check(t.Name, Equals, "Dummy task") - response, _ = s.HTTPRequest("GET", "/api/tasks-wait", nil) - c.Check(response.Code, Equals, 200) - response, _ = s.HTTPRequest("GET", "/api/tasks", nil) - c.Check(response.Code, Equals, 200) - var ts []task.Task - err = json.Unmarshal(response.Body.Bytes(), &ts) - c.Assert(err, IsNil) - c.Check(len(ts), Equals, 1) - c.Check(ts[0].State, Equals, task.SUCCEEDED) - response, _ = s.HTTPRequest("POST", "/api/tasks-clear", nil) - c.Check(response.Code, Equals, 200) - response, _ = s.HTTPRequest("GET", "/api/tasks", nil) - c.Check(response.Code, Equals, 200) - c.Check(response.Body.String(), Equals, "null") -} diff --git a/aptly-api.service b/aptly-api.service deleted file mode 100644 index 40fe7879..00000000 --- a/aptly-api.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=APT repository API -After=network.target -Documentation=man:aptly(1) -Documentation=https://www.aptly.info/doc/api/ - -[Service] -Type=simple -ExecStart=/usr/bin/aptly api serve -no-lock -listen=127.0.0.1:8081 - -[Install] -WantedBy=multi-user.target diff --git a/aptly.service b/aptly.service deleted file mode 100644 index a22e56ca..00000000 --- a/aptly.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=APT repository server -After=network.target -Documentation=man:aptly(1) -Documentation=https://www.aptly.info/doc/commands/ - -[Service] -Type=simple -ExecStart=/usr/bin/aptly serve -listen=127.0.0.1:8080 - -[Install] -WantedBy=multi-user.target diff --git a/aptly/conf.go b/aptly/conf.go new file mode 100644 index 00000000..b39c2b8b --- /dev/null +++ b/aptly/conf.go @@ -0,0 +1,4 @@ +package aptly + +// Default aptly.conf (filled in at link time) +var AptlyConf []byte diff --git a/aptly/const.go b/aptly/const.go new file mode 100644 index 00000000..9cca27ff --- /dev/null +++ b/aptly/const.go @@ -0,0 +1,3 @@ +package aptly + +var DistributionFocal = "focal" diff --git a/aptly/interfaces.go b/aptly/interfaces.go index e912daba..412daecd 100644 --- a/aptly/interfaces.go +++ b/aptly/interfaces.go @@ -35,8 +35,8 @@ type PackagePool interface { Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage ChecksumStorage) (path string, err error) // LegacyPath returns legacy (pre 1.1) path to package file (relative to root) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) - // Stat returns Unix stat(2) info - Stat(path string) (os.FileInfo, error) + // Size returns the size of the given file in bytes. + Size(path string) (size int64, err error) // Open returns ReadSeekerCloser to access the file Open(path string) (ReadSeekerCloser, error) // FilepathList returns file paths of all the files in the pool @@ -47,6 +47,8 @@ type PackagePool interface { // LocalPackagePool is implemented by PackagePools residing on the same filesystem type LocalPackagePool interface { + // Stat returns Unix stat(2) info + Stat(path string) (os.FileInfo, error) // GenerateTempPath generates temporary path for download (which is fast to import into package pool later on) GenerateTempPath(filename string) (string, error) // Link generates hardlink to destination path @@ -70,7 +72,7 @@ type PublishedStorage interface { // Remove removes single file under public path Remove(path string) error // LinkFromPool links package file from pool to dist's pool location - LinkFromPool(publishedDirectory, fileName string, sourcePool PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error + LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error // Filelist returns list of files under prefix Filelist(prefix string) ([]string, error) // RenameFile renames (moves) file diff --git a/azure/azure.go b/azure/azure.go index 5f76b744..3f12678b 100644 --- a/azure/azure.go +++ b/azure/azure.go @@ -1,2 +1,136 @@ -// Package azure handles publishing to Azure Storage package azure + +// Package azure handles publishing to Azure Storage + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/aptly-dev/aptly/aptly" +) + +func isBlobNotFound(err error) bool { + var respErr *azcore.ResponseError + if errors.As(err, &respErr) { + return respErr.StatusCode == 404 // BlobNotFound + } + return false +} + +type azContext struct { + client *azblob.Client + container string + prefix string +} + +func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) { + cred, err := azblob.NewSharedKeyCredential(accountName, accountKey) + if err != nil { + return nil, err + } + + if endpoint == "" { + endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName) + } + + serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil) + if err != nil { + return nil, err + } + + result := &azContext{ + client: serviceClient, + container: container, + prefix: prefix, + } + + return result, nil +} + +func (az *azContext) blobPath(path string) string { + return filepath.Join(az.prefix, path) +} + +func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) { + const delimiter = "/" + paths = make([]string, 0, 1024) + md5s = make([]string, 0, 1024) + prefix = filepath.Join(az.prefix, prefix) + if prefix != "" { + prefix += delimiter + } + + ctx := context.Background() + maxResults := int32(1) + pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{ + Prefix: &prefix, + MaxResults: &maxResults, + Include: azblob.ListBlobsInclude{Metadata: true}, + }) + + // Iterate over each page + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err) + } + + for _, blob := range page.Segment.BlobItems { + if prefix == "" { + paths = append(paths, *blob.Name) + } else { + name := *blob.Name + paths = append(paths, name[len(prefix):]) + } + b := *blob + md5 := b.Properties.ContentMD5 + md5s = append(md5s, fmt.Sprintf("%x", md5)) + + } + if progress != nil { + time.Sleep(time.Duration(500) * time.Millisecond) + progress.AddBar(1) + } + } + + return paths, md5s, nil +} + +func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error { + uploadOptions := &azblob.UploadFileOptions{ + BlockSize: 4 * 1024 * 1024, + Concurrency: 8, + } + + path := az.blobPath(blobName) + if len(sourceMD5) > 0 { + decodedMD5, err := hex.DecodeString(sourceMD5) + if err != nil { + return err + } + uploadOptions.HTTPHeaders = &blob.HTTPHeaders{ + BlobContentMD5: decodedMD5, + } + } + + var err error + if file, ok := source.(*os.File); ok { + _, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions) + } + + return err +} + +// String +func (az *azContext) String() string { + return fmt.Sprintf("Azure: %s/%s", az.container, az.prefix) +} diff --git a/azure/package_pool.go b/azure/package_pool.go new file mode 100644 index 00000000..ad32a11f --- /dev/null +++ b/azure/package_pool.go @@ -0,0 +1,215 @@ +package azure + +import ( + "context" + "os" + "path/filepath" + + "github.com/aptly-dev/aptly/aptly" + "github.com/aptly-dev/aptly/utils" + "github.com/pkg/errors" +) + +type PackagePool struct { + az *azContext +} + +// Check interface +var ( + _ aptly.PackagePool = (*PackagePool)(nil) +) + +// NewPackagePool creates published storage from Azure storage credentials +func NewPackagePool(accountName, accountKey, container, prefix, endpoint string) (*PackagePool, error) { + azctx, err := newAzContext(accountName, accountKey, container, prefix, endpoint) + if err != nil { + return nil, err + } + + return &PackagePool{az: azctx}, nil +} + +// String +func (pool *PackagePool) String() string { + return pool.az.String() +} + +func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.ChecksumInfo) string { + hash := checksums.SHA256 + // Use the same path as the file pool, for compat reasons. + return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename) +} + +func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) { + targetChecksums, err := checksumStorage.Get(poolPath) + if err != nil { + return nil, err + } + + if targetChecksums == nil { + // we don't have checksums stored yet for this file + download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil) + if err != nil { + if isBlobNotFound(err) { + return nil, nil + } + + return nil, errors.Wrapf(err, "error downloading blob at %s", poolPath) + } + + targetChecksums = &utils.ChecksumInfo{} + *targetChecksums, err = utils.ChecksumsForReader(download.Body) + if err != nil { + return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath) + } + + err = checksumStorage.Update(poolPath, targetChecksums) + if err != nil { + return nil, err + } + } + + return targetChecksums, nil +} + +func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) { + if progress != nil { + progress.InitBar(0, false, aptly.BarGeneralBuildFileList) + defer progress.ShutdownBar() + } + + paths, _, err := pool.az.internalFilelist("", progress) + return paths, err +} + +func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, error) { + return "", errors.New("Azure package pool does not support legacy paths") +} + +func (pool *PackagePool) Size(path string) (int64, error) { + serviceClient := pool.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(pool.az.container) + blobClient := containerClient.NewBlobClient(path) + + props, err := blobClient.GetProperties(context.TODO(), nil) + if err != nil { + return 0, errors.Wrapf(err, "error examining %s from %s", path, pool) + } + + return *props.ContentLength, nil +} + +func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) { + temp, err := os.CreateTemp("", "blob-download") + if err != nil { + return nil, errors.Wrapf(err, "error creating tempfile for %s", path) + } + defer os.Remove(temp.Name()) + + _, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil) + if err != nil { + return nil, errors.Wrapf(err, "error downloading blob %s", path) + } + + return temp, nil +} + +func (pool *PackagePool) Remove(path string) (int64, error) { + serviceClient := pool.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(pool.az.container) + blobClient := containerClient.NewBlobClient(path) + + props, err := blobClient.GetProperties(context.TODO(), nil) + if err != nil { + return 0, errors.Wrapf(err, "error examining %s from %s", path, pool) + } + + _, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil) + if err != nil { + return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool) + } + + return *props.ContentLength, nil +} + +func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) { + if checksums.MD5 == "" || checksums.SHA256 == "" || checksums.SHA512 == "" { + // need to update checksums, MD5 and SHA256 should be always defined + var err error + *checksums, err = utils.ChecksumsForFile(srcPath) + if err != nil { + return "", err + } + } + + path := pool.buildPoolPath(basename, checksums) + targetChecksums, err := pool.ensureChecksums(path, checksumStorage) + if err != nil { + return "", err + } else if targetChecksums != nil { + // target already exists + *checksums = *targetChecksums + return path, nil + } + + source, err := os.Open(srcPath) + if err != nil { + return "", err + } + defer source.Close() + + err = pool.az.putFile(path, source, checksums.MD5) + if err != nil { + return "", err + } + + if !checksums.Complete() { + // need full checksums here + *checksums, err = utils.ChecksumsForFile(srcPath) + if err != nil { + return "", err + } + } + + err = checksumStorage.Update(path, checksums) + if err != nil { + return "", err + } + + return path, nil +} + +func (pool *PackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { + if poolPath == "" { + if checksums.SHA256 != "" { + poolPath = pool.buildPoolPath(basename, checksums) + } else { + // No checksums or pool path, so no idea what file to look for. + return "", false, nil + } + } + + size, err := pool.Size(poolPath) + if err != nil { + return "", false, err + } else if size != checksums.Size { + return "", false, nil + } + + targetChecksums, err := pool.ensureChecksums(poolPath, checksumStorage) + if err != nil { + return "", false, err + } else if targetChecksums == nil { + return "", false, nil + } + + if checksums.MD5 != "" && targetChecksums.MD5 != checksums.MD5 || + checksums.SHA256 != "" && targetChecksums.SHA256 != checksums.SHA256 { + // wrong file? + return "", false, nil + } + + // fill back checksums + *checksums = *targetChecksums + return poolPath, true, nil +} diff --git a/azure/package_pool_test.go b/azure/package_pool_test.go new file mode 100644 index 00000000..a32bbff7 --- /dev/null +++ b/azure/package_pool_test.go @@ -0,0 +1,257 @@ +package azure + +import ( + "context" + "io/ioutil" + "os" + "path/filepath" + "runtime" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/aptly-dev/aptly/aptly" + "github.com/aptly-dev/aptly/files" + "github.com/aptly-dev/aptly/utils" + + . "gopkg.in/check.v1" +) + +type PackagePoolSuite struct { + accountName, accountKey, endpoint string + pool, prefixedPool *PackagePool + debFile string + cs aptly.ChecksumStorage +} + +var _ = Suite(&PackagePoolSuite{}) + +func (s *PackagePoolSuite) SetUpSuite(c *C) { + s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT") + if s.accountName == "" { + println("Please set the the following two environment variables to run the Azure storage tests.") + println(" 1. AZURE_STORAGE_ACCOUNT") + println(" 2. AZURE_STORAGE_ACCESS_KEY") + c.Skip("AZURE_STORAGE_ACCOUNT not set.") + } + s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY") + if s.accountKey == "" { + println("Please set the the following two environment variables to run the Azure storage tests.") + println(" 1. AZURE_STORAGE_ACCOUNT") + println(" 2. AZURE_STORAGE_ACCESS_KEY") + c.Skip("AZURE_STORAGE_ACCESS_KEY not set.") + } + s.endpoint = os.Getenv("AZURE_STORAGE_ENDPOINT") +} + +func (s *PackagePoolSuite) SetUpTest(c *C) { + container := randContainer() + prefix := "lala" + + var err error + + s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint) + c.Assert(err, IsNil) + publicAccessType := azblob.PublicAccessTypeContainer + _, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{ + Access: &publicAccessType, + }) + c.Assert(err, IsNil) + + s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint) + c.Assert(err, IsNil) + + _, _File, _, _ := runtime.Caller(0) + s.debFile = filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb") + s.cs = files.NewMockChecksumStorage() +} + +func (s *PackagePoolSuite) TestFilepathList(c *C) { + list, err := s.pool.FilepathList(nil) + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{}) + + s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs) + s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs) + + list, err = s.pool.FilepathList(nil) + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{ + "c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb", + "c7/6b/4bd12fd92e4dfe1b55b18a67a669_b.deb", + }) +} + +func (s *PackagePoolSuite) TestRemove(c *C) { + s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs) + s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs) + + size, err := s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb") + c.Check(err, IsNil) + c.Check(size, Equals, int64(2738)) + + _, err = s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb") + c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*") + + list, err := s.pool.FilepathList(nil) + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{"c7/6b/4bd12fd92e4dfe1b55b18a67a669_b.deb"}) +} + +func (s *PackagePoolSuite) TestImportOk(c *C) { + var checksum utils.ChecksumInfo + path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs) + c.Check(err, IsNil) + c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb") + // SHA256 should be automatically calculated + c.Check(checksum.SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") + // checksum storage is filled with new checksum + c.Check(s.cs.(*files.MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") + + size, err := s.pool.Size(path) + c.Assert(err, IsNil) + c.Check(size, Equals, int64(2738)) + + // import as different name + checksum = utils.ChecksumInfo{} + path, err = s.pool.Import(s.debFile, "some.deb", &checksum, false, s.cs) + c.Check(err, IsNil) + c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_some.deb") + // checksum storage is filled with new checksum + c.Check(s.cs.(*files.MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") + + // double import, should be ok + checksum = utils.ChecksumInfo{} + path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs) + c.Check(err, IsNil) + c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb") + // checksum is filled back based on checksum storage + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // clear checksum storage, and do double-import + delete(s.cs.(*files.MockChecksumStorage).Store, path) + checksum = utils.ChecksumInfo{} + path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs) + c.Check(err, IsNil) + c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb") + // checksum is filled back based on re-calculation of file in the pool + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // import under new name, but with path-relevant checksums already filled in + checksum = utils.ChecksumInfo{SHA256: checksum.SHA256} + path, err = s.pool.Import(s.debFile, "other.deb", &checksum, false, s.cs) + c.Check(err, IsNil) + c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_other.deb") + // checksum is filled back based on re-calculation of source file + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") +} + +func (s *PackagePoolSuite) TestVerify(c *C) { + // file doesn't exist yet + ppath, exists, err := s.pool.Verify("", filepath.Base(s.debFile), &utils.ChecksumInfo{}, s.cs) + c.Check(ppath, Equals, "") + c.Check(err, IsNil) + c.Check(exists, Equals, false) + + // import file + checksum := utils.ChecksumInfo{} + path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs) + c.Check(err, IsNil) + c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb") + + // check existence + ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs) + c.Check(ppath, Equals, ppath) + c.Check(err, IsNil) + c.Check(exists, Equals, true) + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // check existence with fixed path + checksum = utils.ChecksumInfo{Size: checksum.Size} + ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs) + c.Check(ppath, Equals, path) + c.Check(err, IsNil) + c.Check(exists, Equals, true) + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // check existence, but with checksums missing (that aren't needed to find the path) + checksum.SHA512 = "" + ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs) + c.Check(ppath, Equals, path) + c.Check(err, IsNil) + c.Check(exists, Equals, true) + // checksum is filled back based on checksum storage + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // check existence, with missing checksum info but correct path and size available + checksum = utils.ChecksumInfo{Size: checksum.Size} + ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs) + c.Check(ppath, Equals, path) + c.Check(err, IsNil) + c.Check(exists, Equals, true) + // checksum is filled back based on checksum storage + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // check existence, with wrong checksum info but correct path and size available + ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &utils.ChecksumInfo{ + SHA256: "abc", + Size: checksum.Size, + }, s.cs) + c.Check(ppath, Equals, "") + c.Check(err, IsNil) + c.Check(exists, Equals, false) + + // check existence, with missing checksums (that aren't needed to find the path) + // and no info in checksum storage + delete(s.cs.(*files.MockChecksumStorage).Store, path) + checksum.SHA512 = "" + ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs) + c.Check(ppath, Equals, path) + c.Check(err, IsNil) + c.Check(exists, Equals, true) + // checksum is filled back based on re-calculation + c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") + + // check existence, with wrong size + checksum = utils.ChecksumInfo{Size: 13455} + ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs) + c.Check(ppath, Equals, "") + c.Check(err, IsNil) + c.Check(exists, Equals, false) + + // check existence, with empty checksum info + ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &utils.ChecksumInfo{}, s.cs) + c.Check(ppath, Equals, "") + c.Check(err, IsNil) + c.Check(exists, Equals, false) +} + +func (s *PackagePoolSuite) TestImportNotExist(c *C) { + _, err := s.pool.Import("no-such-file", "a.deb", &utils.ChecksumInfo{}, false, s.cs) + c.Check(err, ErrorMatches, ".*no such file or directory") +} + +func (s *PackagePoolSuite) TestSize(c *C) { + path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &utils.ChecksumInfo{}, false, s.cs) + c.Check(err, IsNil) + + size, err := s.pool.Size(path) + c.Assert(err, IsNil) + c.Check(size, Equals, int64(2738)) + + _, err = s.pool.Size("do/es/ntexist") + c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*") +} + +func (s *PackagePoolSuite) TestOpen(c *C) { + path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &utils.ChecksumInfo{}, false, s.cs) + c.Check(err, IsNil) + + f, err := s.pool.Open(path) + c.Assert(err, IsNil) + contents, err := ioutil.ReadAll(f) + c.Assert(err, IsNil) + c.Check(len(contents), Equals, 2738) + c.Check(f.Close(), IsNil) + + _, err = s.pool.Open("do/es/ntexist") + c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*") +} diff --git a/azure/public.go b/azure/public.go index cf1a7b20..6bda2599 100644 --- a/azure/public.go +++ b/azure/public.go @@ -2,27 +2,25 @@ package azure import ( "context" - "encoding/hex" "fmt" - "io" - "net" - "net/http" - "net/url" "os" "path/filepath" "time" - "github.com/Azure/azure-storage-blob-go/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/utils" + "github.com/pborman/uuid" "github.com/pkg/errors" ) // PublishedStorage abstract file system with published files (actually hosted on Azure) type PublishedStorage struct { - container azblob.ContainerURL prefix string - pathCache map[string]string + az *azContext + pathCache map[string]map[string]string } // Check interface @@ -30,60 +28,23 @@ var ( _ aptly.PublishedStorage = (*PublishedStorage)(nil) ) -func isEmulatorEndpoint(endpoint string) bool { - if h, _, err := net.SplitHostPort(endpoint); err == nil { - endpoint = h - } - if endpoint == "localhost" { - return true - } - // For IPv6, there could be case where SplitHostPort fails for cannot finding port. - // In this case, eliminate the '[' and ']' in the URL. - // For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732 - if endpoint[0] == '[' && endpoint[len(endpoint)-1] == ']' { - endpoint = endpoint[1 : len(endpoint)-1] - } - return net.ParseIP(endpoint) != nil -} - // NewPublishedStorage creates published storage from Azure storage credentials func NewPublishedStorage(accountName, accountKey, container, prefix, endpoint string) (*PublishedStorage, error) { - credential, err := azblob.NewSharedKeyCredential(accountName, accountKey) + azctx, err := newAzContext(accountName, accountKey, container, prefix, endpoint) if err != nil { return nil, err } - if endpoint == "" { - endpoint = "blob.core.windows.net" - } - - var url *url.URL - if isEmulatorEndpoint(endpoint) { - url, err = url.Parse(fmt.Sprintf("http://%s/%s/%s", endpoint, accountName, container)) - } else { - url, err = url.Parse(fmt.Sprintf("https://%s.%s/%s", accountName, endpoint, container)) - } - if err != nil { - return nil, err - } - - containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{})) - - result := &PublishedStorage{ - container: containerURL, - prefix: prefix, - } - - return result, nil + return &PublishedStorage{az: azctx}, nil } // String func (storage *PublishedStorage) String() string { - return fmt.Sprintf("Azure: %s/%s", storage.container, storage.prefix) + return storage.az.String() } // MkDir creates directory recursively under public path -func (storage *PublishedStorage) MkDir(path string) error { +func (storage *PublishedStorage) MkDir(_ string) error { // no op for Azure return nil } @@ -106,7 +67,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err } defer source.Close() - err = storage.putFile(path, source, sourceMD5) + err = storage.az.putFile(path, source, sourceMD5) if err != nil { err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage)) } @@ -114,46 +75,17 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err return err } -// putFile uploads file-like object to -func (storage *PublishedStorage) putFile(path string, source io.Reader, sourceMD5 string) error { - path = filepath.Join(storage.prefix, path) - - blob := storage.container.NewBlockBlobURL(path) - - uploadOptions := azblob.UploadStreamToBlockBlobOptions{ - BufferSize: 4 * 1024 * 1024, - MaxBuffers: 8, - } - if len(sourceMD5) > 0 { - decodedMD5, err := hex.DecodeString(sourceMD5) - if err != nil { - return err - } - uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{ - ContentMD5: decodedMD5, - } - } - - _, err := azblob.UploadStreamToBlockBlob( - context.Background(), - source, - blob, - uploadOptions, - ) - - return err -} - // RemoveDirs removes directory structure under public path -func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error { +func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error { + path = storage.az.blobPath(path) filelist, err := storage.Filelist(path) if err != nil { return err } for _, filename := range filelist { - blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path, filename)) - _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{}) + blob := filepath.Join(path, filename) + _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil) if err != nil { return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err) } @@ -164,8 +96,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress // Remove removes single file under public path func (storage *PublishedStorage) Remove(path string) error { - blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path)) - _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{}) + path = storage.az.blobPath(path) + _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil) if err != nil { err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err)) } @@ -174,31 +106,38 @@ func (storage *PublishedStorage) Remove(path string) error { // LinkFromPool links package file from pool to dist's pool location // -// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) +// publishedPrefix is desired prefix for the location in the pool. +// publishedRelPath is desired location in pool (like pool/component/liba/libav/) // sourcePool is instance of aptly.PackagePool // sourcePath is filepath to package file in package pool // // LinkFromPool returns relative path for the published file to be included in package index -func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool, +func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error { - relPath := filepath.Join(publishedDirectory, fileName) - poolPath := filepath.Join(storage.prefix, relPath) + relFilePath := filepath.Join(publishedRelPath, fileName) + prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath) + poolPath := storage.az.blobPath(prefixRelFilePath) if storage.pathCache == nil { - paths, md5s, err := storage.internalFilelist("") + storage.pathCache = make(map[string]map[string]string) + } + pathCache := storage.pathCache[publishedPrefix] + if pathCache == nil { + paths, md5s, err := storage.az.internalFilelist(publishedPrefix, nil) if err != nil { return fmt.Errorf("error caching paths under prefix: %s", err) } - storage.pathCache = make(map[string]string, len(paths)) + pathCache = make(map[string]string, len(paths)) for i := range paths { - storage.pathCache[paths[i]] = md5s[i] + pathCache[paths[i]] = md5s[i] } + storage.pathCache[publishedPrefix] = pathCache } - destinationMD5, exists := storage.pathCache[relPath] + destinationMD5, exists := pathCache[relFilePath] sourceMD5 := sourceChecksums.MD5 if exists { @@ -221,9 +160,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin } defer source.Close() - err = storage.putFile(relPath, source, sourceMD5) + err = storage.az.putFile(relFilePath, source, sourceMD5) if err == nil { - storage.pathCache[relPath] = sourceMD5 + pathCache[relFilePath] = sourceMD5 } else { err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s: %s", sourcePath, storage, poolPath)) } @@ -231,98 +170,65 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin return err } -func (storage *PublishedStorage) internalFilelist(prefix string) (paths []string, md5s []string, err error) { - const delimiter = "/" - paths = make([]string, 0, 1024) - md5s = make([]string, 0, 1024) - prefix = filepath.Join(storage.prefix, prefix) - if prefix != "" { - prefix += delimiter - } - - for marker := (azblob.Marker{}); marker.NotDone(); { - listBlob, err := storage.container.ListBlobsFlatSegment( - context.Background(), marker, azblob.ListBlobsSegmentOptions{ - Prefix: prefix, - MaxResults: 1000, - Details: azblob.BlobListingDetails{Metadata: true}}) - if err != nil { - return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, storage, err) - } - - marker = listBlob.NextMarker - - for _, blob := range listBlob.Segment.BlobItems { - if prefix == "" { - paths = append(paths, blob.Name) - } else { - paths = append(paths, blob.Name[len(prefix):]) - } - md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5)) - } - } - - return paths, md5s, nil -} - // Filelist returns list of files under prefix func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { - paths, _, err := storage.internalFilelist(prefix) + paths, _, err := storage.az.internalFilelist(prefix, nil) return paths, err } // Internal copy or move implementation -func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error { +func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error { const leaseDuration = 30 + leaseID := uuid.NewRandom().String() - dstBlobURL := storage.container.NewBlobURL(filepath.Join(storage.prefix, dst)) - srcBlobURL := storage.container.NewBlobURL(filepath.Join(storage.prefix, src)) - leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{}) - if err != nil || leaseResp.StatusCode() != http.StatusCreated { - return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL) + serviceClient := storage.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(storage.az.container) + srcBlobClient := containerClient.NewBlobClient(src) + blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)}) + if err != nil { + return fmt.Errorf("error acquiring lease on source blob %s", src) } - defer srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{}) - srcBlobLeaseID := leaseResp.LeaseID() - copyResp, err := dstBlobURL.StartCopyFromURL( - context.Background(), - srcBlobURL.URL(), - metadata, - azblob.ModifiedAccessConditions{}, - azblob.BlobAccessConditions{}, - azblob.DefaultAccessTier, - nil) + _, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil) + if err != nil { + return fmt.Errorf("error acquiring lease on source blob %s", src) + } + defer blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))}) + + dstBlobClient := containerClient.NewBlobClient(dst) + copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{ + Metadata: metadata, + }) + if err != nil { return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err) } - copyStatus := copyResp.CopyStatus() + copyStatus := *copyResp.CopyStatus for { - if copyStatus == azblob.CopyStatusSuccess { + if copyStatus == blob.CopyStatusTypeSuccess { if move { - _, err = srcBlobURL.Delete( - context.Background(), - azblob.DeleteSnapshotsOptionNone, - azblob.BlobAccessConditions{ - LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}, - }) + _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{ + AccessConditions: &blob.AccessConditions{ + LeaseAccessConditions: &blob.LeaseAccessConditions{ + LeaseID: &leaseID, + }, + }, + }) return err } return nil - } else if copyStatus == azblob.CopyStatusPending { + } else if copyStatus == blob.CopyStatusTypePending { time.Sleep(1 * time.Second) - blobPropsResp, err := dstBlobURL.GetProperties( - context.Background(), - azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}}, - azblob.ClientProvidedKeyOptions{}) + getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil) if err != nil { - return fmt.Errorf("error getting destination blob properties %s", dstBlobURL) + return fmt.Errorf("error getting copy progress %s", dst) } - copyStatus = blobPropsResp.CopyStatus() + copyStatus = *getMetadata.CopyStatus - _, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{}) + _, err = blobLeaseClient.RenewLease(context.Background(), nil) if err != nil { - return fmt.Errorf("error renewing source blob lease %s", srcBlobURL) + return fmt.Errorf("error renewing source blob lease %s", src) } } else { return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus) @@ -337,7 +243,9 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error { // SymLink creates a copy of src file and adds link information as meta data func (storage *PublishedStorage) SymLink(src string, dst string) error { - return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */) + metadata := make(map[string]*string) + metadata["SymLink"] = &src + return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */) } // HardLink using symlink functionality as hard links do not exist @@ -347,29 +255,33 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error { // FileExists returns true if path exists func (storage *PublishedStorage) FileExists(path string) (bool, error) { - blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path)) - resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{}) + serviceClient := storage.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(storage.az.container) + blobClient := containerClient.NewBlobClient(path) + _, err := blobClient.GetProperties(context.Background(), nil) if err != nil { - storageError, ok := err.(azblob.StorageError) - if ok && string(storageError.ServiceCode()) == string(azblob.StorageErrorCodeBlobNotFound) { + if isBlobNotFound(err) { return false, nil } - return false, err - } else if resp.StatusCode() == http.StatusOK { - return true, nil + return false, fmt.Errorf("error checking if blob %s exists: %v", path, err) } - return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode()) + return true, nil } // ReadLink returns the symbolic link pointed to by path. // This simply reads text file created with SymLink func (storage *PublishedStorage) ReadLink(path string) (string, error) { - blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path)) - resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{}) + serviceClient := storage.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(storage.az.container) + blobClient := containerClient.NewBlobClient(path) + props, err := blobClient.GetProperties(context.Background(), nil) if err != nil { - return "", err - } else if resp.StatusCode() != http.StatusOK { - return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode()) + return "", fmt.Errorf("failed to get blob properties: %v", err) } - return resp.NewMetadata()["SymLink"], nil + + metadata := props.Metadata + if originalBlob, exists := metadata["original_blob"]; exists { + return *originalBlob, nil + } + return "", fmt.Errorf("error reading link %s: %v", path, err) } diff --git a/azure/public_test.go b/azure/public_test.go index 79abfed2..3acd767f 100644 --- a/azure/public_test.go +++ b/azure/public_test.go @@ -7,8 +7,11 @@ import ( "io/ioutil" "os" "path/filepath" + "bytes" - "github.com/Azure/azure-storage-blob-go/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/aptly-dev/aptly/files" "github.com/aptly-dev/aptly/utils" . "gopkg.in/check.v1" @@ -43,14 +46,14 @@ func randString(n int) string { func (s *PublishedStorageSuite) SetUpSuite(c *C) { s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT") if s.accountName == "" { - println("Please set the the following two environment variables to run the Azure storage tests.") + println("Please set the following two environment variables to run the Azure storage tests.") println(" 1. AZURE_STORAGE_ACCOUNT") println(" 2. AZURE_STORAGE_ACCESS_KEY") c.Skip("AZURE_STORAGE_ACCOUNT not set.") } s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY") if s.accountKey == "" { - println("Please set the the following two environment variables to run the Azure storage tests.") + println("Please set the following two environment variables to run the Azure storage tests.") println(" 1. AZURE_STORAGE_ACCOUNT") println(" 2. AZURE_STORAGE_ACCESS_KEY") c.Skip("AZURE_STORAGE_ACCESS_KEY not set.") @@ -66,8 +69,10 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint) c.Assert(err, IsNil) - cnt := s.storage.container - _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer) + publicAccessType := azblob.PublicAccessTypeContainer + _, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{ + Access: &publicAccessType, + }) c.Assert(err, IsNil) s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint) @@ -75,41 +80,39 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { } func (s *PublishedStorageSuite) TearDownTest(c *C) { - cnt := s.storage.container - _, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{}) + _, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil) c.Assert(err, IsNil) } func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte { - blob := s.storage.container.NewBlobURL(path) - resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{}) + resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil) c.Assert(err, IsNil) - body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3}) - data, err := ioutil.ReadAll(body) + data, err := ioutil.ReadAll(resp.Body) c.Assert(err, IsNil) return data } func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) { - _, err := s.storage.container.NewBlobURL(path).GetProperties( - context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{}) + serviceClient := s.storage.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(s.storage.az.container) + blobClient := containerClient.NewBlobClient(path) + _, err := blobClient.GetProperties(context.Background(), nil) c.Assert(err, NotNil) - storageError, ok := err.(azblob.StorageError) + + storageError, ok := err.(*azcore.ResponseError) c.Assert(ok, Equals, true) - c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound))) + c.Assert(storageError.StatusCode, Equals, 404) } func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) { hash := md5.Sum(data) - _, err := azblob.UploadBufferToBlockBlob( - context.Background(), - data, - s.storage.container.NewBlockBlobURL(path), - azblob.UploadToBlockBlobOptions{ - BlobHTTPHeaders: azblob.BlobHTTPHeaders{ - ContentMD5: hash[:], - }, - }) + uploadOptions := &azblob.UploadStreamOptions{ + HTTPHeaders: &blob.HTTPHeaders{ + BlobContentMD5: hash[:], + }, + } + reader := bytes.NewReader(data) + _, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions) c.Assert(err, IsNil) } @@ -129,7 +132,7 @@ func (s *PublishedStorageSuite) TestPutFile(c *C) { err = s.prefixedStorage.PutFile(filename, filepath.Join(dir, "a")) c.Check(err, IsNil) - c.Check(s.GetFile(c, filepath.Join(s.prefixedStorage.prefix, filename)), DeepEquals, content) + c.Check(s.GetFile(c, filepath.Join(s.prefixedStorage.az.prefix, filename)), DeepEquals, content) } func (s *PublishedStorageSuite) TestPutFilePlus(c *C) { @@ -300,45 +303,45 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Assert(err, IsNil) // first link from pool - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.storage.LinkFromPool("", filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // duplicate link from pool - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // link from pool with conflict - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false) c.Check(err, ErrorMatches, ".*file already exists and is different.*") c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // link from pool with conflict and force - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true) c.Check(err, IsNil) c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Spam")) // for prefixed storage: // first link from pool - err = s.prefixedStorage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) // 2nd link from pool, providing wrong path for source file // - // this test should check that file already exists in S3 and skip upload (which would fail if not skipped) + // this test should check that file already exists in Azure and skip upload (which would fail if not skipped) s.prefixedStorage.pathCache = nil - err = s.prefixedStorage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false) + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "lala/pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // link from pool with nested file name - err = s.storage.LinkFromPool("dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false) + err = s.storage.LinkFromPool("", "dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "dists/jessie/non-free/installer-i386/current/images/netboot/boot.img.gz"), DeepEquals, []byte("Contents")) diff --git a/cmd/api_serve.go b/cmd/api_serve.go index 7c19d2ef..d3ccdf77 100644 --- a/cmd/api_serve.go +++ b/cmd/api_serve.go @@ -1,11 +1,15 @@ package cmd import ( + stdcontext "context" + "errors" "fmt" "net" "net/http" "net/url" "os" + "os/signal" + "syscall" "github.com/aptly-dev/aptly/api" "github.com/aptly-dev/aptly/systemd/activation" @@ -30,7 +34,7 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error { // anything else must fail. // E.g.: Running the service under a different user may lead to a rootDir // that exists but is not usable due to access permissions. - err = utils.DirIsAccessible(context.Config().RootDir) + err = utils.DirIsAccessible(context.Config().GetRootDir()) if err != nil { return err } @@ -55,6 +59,19 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error { listen := context.Flags().Lookup("listen").Value.String() fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen) + server := http.Server{Handler: api.Router(context)} + + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + go (func() { + if _, ok := <-sigchan; ok { + fmt.Printf("\nShutdown signal received, waiting for background tasks...\n") + context.TaskList().Wait() + server.Shutdown(stdcontext.Background()) + } + })() + defer close(sigchan) + listenURL, err := url.Parse(listen) if err == nil && listenURL.Scheme == "unix" { file := listenURL.Path @@ -67,19 +84,17 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error { } defer listener.Close() - err = http.Serve(listener, api.Router(context)) - if err != nil { - return fmt.Errorf("unable to serve: %s", err) - } - return nil + err = server.Serve(listener) + } else { + server.Addr = listen + err = server.ListenAndServe() } - err = http.ListenAndServe(listen, api.Router(context)) - if err != nil { + if err != nil && !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("unable to serve: %s", err) } - return err + return nil } func makeCmdAPIServe() *commander.Command { diff --git a/cmd/cmd.go b/cmd/cmd.go index 14a0efd1..d5bdff25 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -118,7 +118,7 @@ package environment to new version.`, cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if dependency is 'a|b'") cmd.Flag.Bool("dep-verbose-resolve", false, "when processing dependencies, print detailed logs") cmd.Flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available") - cmd.Flag.String("config", "", "location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf)") + cmd.Flag.String("config", "", "location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf)") cmd.Flag.String("gpg-provider", "", "PGP implementation (\"gpg\", \"gpg1\", \"gpg2\" for external gpg or \"internal\" for Go internal implementation)") if aptly.EnableDebug { diff --git a/cmd/config_show.go b/cmd/config_show.go index 119fd37f..6568604f 100644 --- a/cmd/config_show.go +++ b/cmd/config_show.go @@ -5,19 +5,30 @@ import ( "fmt" "github.com/smira/commander" + "gopkg.in/yaml.v3" ) -func aptlyConfigShow(cmd *commander.Command, args []string) error { +func aptlyConfigShow(_ *commander.Command, _ []string) error { + showYaml := context.Flags().Lookup("yaml").Value.Get().(bool) config := context.Config() - prettyJSON, err := json.MarshalIndent(config, "", " ") - if err != nil { - return fmt.Errorf("unable to dump the config file: %s", err) + if showYaml { + yamlData, err := yaml.Marshal(&config) + if err != nil { + return fmt.Errorf("error marshaling to YAML: %s", err) + } + + fmt.Println(string(yamlData)) + } else { + prettyJSON, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("unable to dump the config file: %s", err) + } + + fmt.Println(string(prettyJSON)) } - fmt.Println(string(prettyJSON)) - return nil } @@ -35,5 +46,6 @@ Example: `, } + cmd.Flag.Bool("yaml", false, "show yaml config") return cmd } diff --git a/cmd/graph.go b/cmd/graph.go index 868e5874..64b12d9e 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -4,13 +4,11 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" "runtime" "strings" - "time" "github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/utils" @@ -36,7 +34,7 @@ func aptlyGraph(cmd *commander.Command, args []string) error { buf := bytes.NewBufferString(graph.String()) - tempfile, err := ioutil.TempFile("", "aptly-graph") + tempfile, err := os.CreateTemp("", "aptly-graph") if err != nil { return err } @@ -80,10 +78,6 @@ func aptlyGraph(cmd *commander.Command, args []string) error { return err } - defer func() { - _ = os.Remove(tempfilename) - }() - if output != "" { err = utils.CopyFile(tempfilename, output) if err != nil { @@ -91,23 +85,16 @@ func aptlyGraph(cmd *commander.Command, args []string) error { } fmt.Printf("Output saved to %s\n", output) + _ = os.Remove(tempfilename) } else { command := getOpenCommand() - fmt.Printf("Rendered to %s file: %s, trying to open it with: %s %s...\n", format, tempfilename, command, tempfilename) + fmt.Printf("Displaying %s file: %s %s\n", format, command, tempfilename) args := strings.Split(command, " ") viewer := exec.Command(args[0], append(args[1:], tempfilename)...) viewer.Stderr = os.Stderr - if err = viewer.Start(); err == nil { - // Wait for a second so that the visualizer has a chance to - // open the input file. This needs to be done even if we're - // waiting for the visualizer as it can be just a wrapper that - // spawns a browser tab and returns right away. - defer func(t <-chan time.Time) { - <-t - }(time.After(time.Second)) - } + err = viewer.Start() } return err diff --git a/cmd/mirror.go b/cmd/mirror.go index 8681173e..89b19a6c 100644 --- a/cmd/mirror.go +++ b/cmd/mirror.go @@ -9,18 +9,18 @@ import ( ) func getVerifier(flags *flag.FlagSet) (pgp.Verifier, error) { - if LookupOption(context.Config().GpgDisableVerify, flags, "ignore-signatures") { - return nil, nil - } - keyRings := flags.Lookup("keyring").Value.Get().([]string) + ignoreSignatures := context.Config().GpgDisableVerify + if context.Flags().IsSet("ignore-signatures") { + ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool) + } verifier := context.GetVerifier() for _, keyRing := range keyRings { verifier.AddKeyring(keyRing) } - err := verifier.InitKeyring() + err := verifier.InitKeyring(ignoreSignatures == false) // be verbose only if verifying signatures is requested if err != nil { return nil, err } diff --git a/cmd/mirror_create.go b/cmd/mirror_create.go index 78d91b58..049dd2eb 100644 --- a/cmd/mirror_create.go +++ b/cmd/mirror_create.go @@ -20,6 +20,10 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources") downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool) downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool) + ignoreSignatures := context.Config().GpgDisableVerify + if context.Flags().IsSet("ignore-signatures") { + ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool) + } var ( mirrorName, archiveURL, distribution string @@ -42,7 +46,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to create mirror: %s", err) } - repo.Filter = context.Flags().Lookup("filter").Value.String() + repo.Filter = context.Flags().Lookup("filter").Value.String() // allows file/stdin with @ repo.FilterWithDeps = context.Flags().Lookup("filter-with-deps").Value.Get().(bool) repo.SkipComponentCheck = context.Flags().Lookup("force-components").Value.Get().(bool) repo.SkipArchitectureCheck = context.Flags().Lookup("force-architectures").Value.Get().(bool) @@ -59,7 +63,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to initialize GPG verifier: %s", err) } - err = repo.Fetch(context.Downloader(), verifier) + err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures) if err != nil { return fmt.Errorf("unable to fetch mirror: %s", err) } @@ -99,7 +103,7 @@ Example: cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files") cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages") cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)") - cmd.Flag.String("filter", "", "filter packages in mirror") + AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin") cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well") cmd.Flag.Bool("force-components", false, "(only with component list) skip check that requested components are listed in Release file") cmd.Flag.Bool("force-architectures", false, "(only with architecture list) skip check that requested architectures are listed in Release file") diff --git a/cmd/mirror_edit.go b/cmd/mirror_edit.go index 86462c4c..f95d9736 100644 --- a/cmd/mirror_edit.go +++ b/cmd/mirror_edit.go @@ -28,10 +28,11 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { } fetchMirror := false + ignoreSignatures := context.Config().GpgDisableVerify context.Flags().Visit(func(flag *flag.Flag) { switch flag.Name { case "filter": - repo.Filter = flag.Value.String() + repo.Filter = flag.Value.String() // allows file/stdin with @ case "filter-with-deps": repo.FilterWithDeps = flag.Value.Get().(bool) case "with-installer": @@ -43,6 +44,8 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { case "archive-url": repo.SetArchiveRoot(flag.Value.String()) fetchMirror = true + case "ignore-signatures": + ignoreSignatures = true } }) @@ -69,7 +72,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to initialize GPG verifier: %s", err) } - err = repo.Fetch(context.Downloader(), verifier) + err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures) if err != nil { return fmt.Errorf("unable to edit: %s", err) } @@ -101,7 +104,7 @@ Example: } cmd.Flag.String("archive-url", "", "archive url is the root of archive") - cmd.Flag.String("filter", "", "filter packages in mirror") + AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin") cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well") cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures") cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files") diff --git a/cmd/mirror_list.go b/cmd/mirror_list.go index 1a54f433..42b802cf 100644 --- a/cmd/mirror_list.go +++ b/cmd/mirror_list.go @@ -24,7 +24,7 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error { return aptlyMirrorListTxt(cmd, args) } -func aptlyMirrorListTxt(cmd *commander.Command, args []string) error { +func aptlyMirrorListTxt(cmd *commander.Command, _ []string) error { var err error raw := cmd.Flag.Lookup("raw").Value.Get().(bool) @@ -65,7 +65,7 @@ func aptlyMirrorListTxt(cmd *commander.Command, args []string) error { return err } -func aptlyMirrorListJSON(cmd *commander.Command, args []string) error { +func aptlyMirrorListJSON(_ *commander.Command, _ []string) error { var err error repos := make([]*deb.RemoteRepo, context.NewCollectionFactory().RemoteRepoCollection().Len()) diff --git a/cmd/mirror_show.go b/cmd/mirror_show.go index 7491b355..03179161 100644 --- a/cmd/mirror_show.go +++ b/cmd/mirror_show.go @@ -27,7 +27,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error { return aptlyMirrorShowTxt(cmd, args) } -func aptlyMirrorShowTxt(cmd *commander.Command, args []string) error { +func aptlyMirrorShowTxt(_ *commander.Command, args []string) error { var err error name := args[0] @@ -93,7 +93,7 @@ func aptlyMirrorShowTxt(cmd *commander.Command, args []string) error { return err } -func aptlyMirrorShowJSON(cmd *commander.Command, args []string) error { +func aptlyMirrorShowJSON(_ *commander.Command, args []string) error { var err error name := args[0] diff --git a/cmd/mirror_update.go b/cmd/mirror_update.go index 111590bb..2aad8c53 100644 --- a/cmd/mirror_update.go +++ b/cmd/mirror_update.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "os" "strings" "sync" @@ -41,20 +42,24 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { } } - ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool) + ignoreSignatures := context.Config().GpgDisableVerify + if context.Flags().IsSet("ignore-signatures") { + ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool) + } + ignoreChecksums := context.Flags().Lookup("ignore-checksums").Value.Get().(bool) verifier, err := getVerifier(context.Flags()) if err != nil { return fmt.Errorf("unable to initialize GPG verifier: %s", err) } - err = repo.Fetch(context.Downloader(), verifier) + err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures) if err != nil { return fmt.Errorf("unable to update: %s", err) } context.Progress().Printf("Downloading & parsing package files...\n") - err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), verifier, collectionFactory, ignoreMismatch) + err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), verifier, collectionFactory, ignoreSignatures, ignoreChecksums) if err != nil { return fmt.Errorf("unable to update: %s", err) } @@ -161,7 +166,16 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { var e error // provision download location - task.TempDownPath, e = context.PackagePool().(aptly.LocalPackagePool).GenerateTempPath(task.File.Filename) + if pp, ok := context.PackagePool().(aptly.LocalPackagePool); ok { + task.TempDownPath, e = pp.GenerateTempPath(task.File.Filename) + } else { + var file *os.File + file, e = os.CreateTemp("", task.File.Filename) + if e == nil { + task.TempDownPath = file.Name() + file.Close() + } + } if e != nil { pushError(e) continue @@ -173,7 +187,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { repo.PackageURL(task.File.DownloadURL()).String(), task.TempDownPath, &task.File.Checksums, - ignoreMismatch) + ignoreChecksums) if e != nil { pushError(e) continue @@ -197,6 +211,18 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } + defer func() { + for _, task := range queue { + if task.TempDownPath == "" { + continue + } + + if err := os.Remove(task.TempDownPath); err != nil && !os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "Failed to delete %s: %v\n", task.TempDownPath, err) + } + } + }() + // Import downloaded files context.Progress().InitBar(int64(len(queue)), false, aptly.BarMirrorUpdateImportFiles) @@ -241,7 +267,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } - context.Progress().Printf("\nMirror `%s` has been successfully updated.\n", repo.Name) + context.Progress().Printf("\nMirror `%s` has been updated successfully.\n", repo.Name) return err } diff --git a/cmd/package_search.go b/cmd/package_search.go index 2105a1f3..54290c21 100644 --- a/cmd/package_search.go +++ b/cmd/package_search.go @@ -21,7 +21,11 @@ func aptlyPackageSearch(cmd *commander.Command, args []string) error { } if len(args) == 1 { - q, err = query.Parse(args[0]) + value, err := GetStringOrFileContent(args[0]) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", args[0], err) + } + q, err = query.Parse(value) if err != nil { return fmt.Errorf("unable to search: %s", err) } @@ -49,6 +53,7 @@ func makeCmdPackageSearch() *commander.Command { Long: ` Command search displays list of packages in whole DB that match package query. +Use '@file' to read query from file or '@-' for stdin. If query is not specified, all the packages are displayed. Example: diff --git a/cmd/package_show.go b/cmd/package_show.go index 37f07e9b..1715b52a 100644 --- a/cmd/package_show.go +++ b/cmd/package_show.go @@ -66,7 +66,11 @@ func aptlyPackageShow(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - q, err := query.Parse(args[0]) + value, err := GetStringOrFileContent(args[0]) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", args[0], err) + } + q, err := query.Parse(value) if err != nil { return fmt.Errorf("unable to show: %s", err) } @@ -130,6 +134,8 @@ matching query. Information from Debian control file is displayed. Optionally information about package files and inclusion into mirrors/snapshots/local repos is shown. +Use '@file' to read query from file or '@-' for stdin. + Example: $ aptly package show 'nginx-light_1.2.1-2.2+wheezy2_i386' diff --git a/cmd/publish.go b/cmd/publish.go index d74384e0..4217ff87 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -34,10 +34,26 @@ func makeCmdPublish() *commander.Command { makeCmdPublishDrop(), makeCmdPublishList(), makeCmdPublishRepo(), + makeCmdPublishShow(), makeCmdPublishSnapshot(), + makeCmdPublishSource(), makeCmdPublishSwitch(), makeCmdPublishUpdate(), - makeCmdPublishShow(), + }, + } +} + +func makeCmdPublishSource() *commander.Command { + return &commander.Command{ + UsageLine: "source", + Short: "manage sources of published repository", + Subcommands: []*commander.Command{ + makeCmdPublishSourceAdd(), + makeCmdPublishSourceDrop(), + makeCmdPublishSourceList(), + makeCmdPublishSourceRemove(), + makeCmdPublishSourceReplace(), + makeCmdPublishSourceUpdate(), }, } } diff --git a/cmd/publish_list.go b/cmd/publish_list.go index a4e0281f..e3a1d1a8 100644 --- a/cmd/publish_list.go +++ b/cmd/publish_list.go @@ -25,7 +25,7 @@ func aptlyPublishList(cmd *commander.Command, args []string) error { return aptlyPublishListTxt(cmd, args) } -func aptlyPublishListTxt(cmd *commander.Command, args []string) error { +func aptlyPublishListTxt(cmd *commander.Command, _ []string) error { var err error raw := cmd.Flag.Lookup("raw").Value.Get().(bool) @@ -34,7 +34,7 @@ func aptlyPublishListTxt(cmd *commander.Command, args []string) error { published := make([]string, 0, collectionFactory.PublishedRepoCollection().Len()) err = collectionFactory.PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error { - e := collectionFactory.PublishedRepoCollection().LoadComplete(repo, collectionFactory) + e := collectionFactory.PublishedRepoCollection().LoadShallow(repo, collectionFactory) if e != nil { fmt.Fprintf(os.Stderr, "Error found on one publish (prefix:%s / distribution:%s / component:%s\n)", repo.StoragePrefix(), repo.Distribution, repo.Components()) @@ -77,7 +77,7 @@ func aptlyPublishListTxt(cmd *commander.Command, args []string) error { return err } -func aptlyPublishListJSON(cmd *commander.Command, args []string) error { +func aptlyPublishListJSON(_ *commander.Command, _ []string) error { var err error repos := make([]*deb.PublishedRepo, 0, context.NewCollectionFactory().PublishedRepoCollection().Len()) diff --git a/cmd/publish_repo.go b/cmd/publish_repo.go index 3e357cb8..91971022 100644 --- a/cmd/publish_repo.go +++ b/cmd/publish_repo.go @@ -48,8 +48,10 @@ Example: cmd.Flag.String("butautomaticupgrades", "", "set value for ButAutomaticUpgrades field") cmd.Flag.String("label", "", "label to publish") cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)") + cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash") + cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions") return cmd } diff --git a/cmd/publish_show.go b/cmd/publish_show.go index 3b9ec4e3..a0af446b 100644 --- a/cmd/publish_show.go +++ b/cmd/publish_show.go @@ -24,7 +24,7 @@ func aptlyPublishShow(cmd *commander.Command, args []string) error { return aptlyPublishShowTxt(cmd, args) } -func aptlyPublishShowTxt(cmd *commander.Command, args []string) error { +func aptlyPublishShowTxt(_ *commander.Command, args []string) error { var err error distribution := args[0] @@ -52,7 +52,8 @@ func aptlyPublishShowTxt(cmd *commander.Command, args []string) error { fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, " ")) fmt.Printf("Sources:\n") - for component, sourceID := range repo.Sources { + for _, component := range repo.Components() { + sourceID := repo.Sources[component] var name string if repo.SourceKind == deb.SourceSnapshot { source, e := collectionFactory.SnapshotCollection().ByUUID(sourceID) @@ -76,7 +77,7 @@ func aptlyPublishShowTxt(cmd *commander.Command, args []string) error { return err } -func aptlyPublishShowJSON(cmd *commander.Command, args []string) error { +func aptlyPublishShowJSON(_ *commander.Command, args []string) error { var err error distribution := args[0] diff --git a/cmd/publish_snapshot.go b/cmd/publish_snapshot.go index 5b02a6be..eac4c3b3 100644 --- a/cmd/publish_snapshot.go +++ b/cmd/publish_snapshot.go @@ -116,8 +116,9 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { origin := context.Flags().Lookup("origin").Value.String() notAutomatic := context.Flags().Lookup("notautomatic").Value.String() butAutomaticUpgrades := context.Flags().Lookup("butautomaticupgrades").Value.String() + multiDist := context.Flags().Lookup("multi-dist").Value.Get().(bool) - published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, collectionFactory) + published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, collectionFactory, multiDist) if err != nil { return fmt.Errorf("unable to publish: %s", err) } @@ -132,6 +133,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { } published.Label = context.Flags().Lookup("label").Value.String() published.Suite = context.Flags().Lookup("suite").Value.String() + published.Codename = context.Flags().Lookup("codename").Value.String() published.SkipContents = context.Config().SkipContentsPublishing @@ -148,6 +150,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool) } + if context.Flags().IsSet("multi-dist") { + published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool) + } + duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published) if duplicate != nil { collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory) @@ -161,11 +167,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool) if forceOverwrite { - context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + - "the same package pool.\n") + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing the same package pool.\n") } - err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite) + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath()) if err != nil { return fmt.Errorf("unable to publish: %s", err) } @@ -239,8 +244,10 @@ Example: cmd.Flag.String("butautomaticupgrades", "", "overwrite value for ButAutomaticUpgrades field") cmd.Flag.String("label", "", "label to publish") cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)") + cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash") + cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions") return cmd } diff --git a/cmd/publish_source_add.go b/cmd/publish_source_add.go new file mode 100644 index 00000000..b052c668 --- /dev/null +++ b/cmd/publish_source_add.go @@ -0,0 +1,94 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/aptly-dev/aptly/deb" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPublishSourceAdd(cmd *commander.Command, args []string) error { + if len(args) < 2 { + cmd.Usage() + return commander.ErrCommandError + } + + distribution := args[0] + names := args[1:] + components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") + + if len(names) != len(components) { + return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names)) + } + + prefix := context.Flags().Lookup("prefix").Value.String() + storage, prefix := deb.ParsePrefix(prefix) + + collectionFactory := context.NewCollectionFactory() + published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + return fmt.Errorf("unable to add: %s", err) + } + + err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) + if err != nil { + return fmt.Errorf("unable to add: %s", err) + } + + revision := published.ObtainRevision() + sources := revision.Sources + + for i, component := range components { + name := names[i] + _, exists := sources[component] + if exists { + return fmt.Errorf("unable to add: component '%s' has already been added", component) + } + context.Progress().Printf("Adding component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind) + + sources[component] = name + } + + err = collectionFactory.PublishedRepoCollection().Update(published) + if err != nil { + return fmt.Errorf("unable to save to DB: %s", err) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceAdd() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceAdd, + UsageLine: "add ", + Short: "add source components to a published repo", + Long: ` +The command adds components of a snapshot or local repository to be published. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +The flag -component is mandatory. Use a comma-separated list of components, if +multiple components should be modified. The number of given components must be +equal to the number of given sources, e.g.: + + aptly publish source add -component=main,contrib wheezy wheezy-main wheezy-contrib + +Example: + + $ aptly publish source add -component=contrib wheezy ppa wheezy-contrib + +This command assigns the snapshot wheezy-contrib to the component contrib and +adds it to published repository revision of ppa/wheezy. +`, + Flag: *flag.NewFlagSet("aptly-publish-source-add", flag.ExitOnError), + } + cmd.Flag.String("prefix", ".", "publishing prefix in the form of [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_drop.go b/cmd/publish_source_drop.go new file mode 100644 index 00000000..704e36e7 --- /dev/null +++ b/cmd/publish_source_drop.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "fmt" + + "github.com/aptly-dev/aptly/deb" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPublishSourceDrop(cmd *commander.Command, args []string) error { + if len(args) != 1 { + cmd.Usage() + return commander.ErrCommandError + } + + prefix := context.Flags().Lookup("prefix").Value.String() + distribution := args[0] + storage, prefix := deb.ParsePrefix(prefix) + + collectionFactory := context.NewCollectionFactory() + published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + return fmt.Errorf("unable to drop: %s", err) + } + + err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) + if err != nil { + return fmt.Errorf("unable to drop: %s", err) + } + + published.DropRevision() + + err = collectionFactory.PublishedRepoCollection().Update(published) + if err != nil { + return fmt.Errorf("unable to save to DB: %s", err) + } + + context.Progress().Printf("Source changes have been removed successfully.\n") + + return err +} + +func makeCmdPublishSourceDrop() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceDrop, + UsageLine: "drop ", + Short: "drop pending source component changes of a published repository", + Long: ` +Remove all pending changes what would be applied with a subsequent 'aptly publish update'. + +Example: + + $ aptly publish source drop wheezy +`, + Flag: *flag.NewFlagSet("aptly-publish-source-drop", flag.ExitOnError), + } + cmd.Flag.String("prefix", ".", "publishing prefix in the form of [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_list.go b/cmd/publish_source_list.go new file mode 100644 index 00000000..aa61a2b8 --- /dev/null +++ b/cmd/publish_source_list.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "encoding/json" + "fmt" + + "github.com/aptly-dev/aptly/deb" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPublishSourceList(cmd *commander.Command, args []string) error { + if len(args) != 1 { + cmd.Usage() + return commander.ErrCommandError + } + + prefix := context.Flags().Lookup("prefix").Value.String() + distribution := args[0] + storage, prefix := deb.ParsePrefix(prefix) + + published, err := context.NewCollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + return fmt.Errorf("unable to list: %s", err) + } + + err = context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(published, context.NewCollectionFactory()) + if err != nil { + return err + } + + if published.Revision == nil { + return fmt.Errorf("unable to list: no source changes exist") + } + + jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool) + + if jsonFlag { + return aptlyPublishSourceListJSON(published) + } + + return aptlyPublishSourceListTxt(published) +} + +func aptlyPublishSourceListTxt(published *deb.PublishedRepo) error { + revision := published.Revision + + fmt.Printf("Sources:\n") + for _, component := range revision.Components() { + name := revision.Sources[component] + fmt.Printf(" %s: %s [%s]\n", component, name, published.SourceKind) + } + + return nil +} + +func aptlyPublishSourceListJSON(published *deb.PublishedRepo) error { + revision := published.Revision + + output, err := json.MarshalIndent(revision.SourceList(), "", " ") + if err != nil { + return fmt.Errorf("unable to list: %s", err) + } + + fmt.Println(string(output)) + + return nil +} + +func makeCmdPublishSourceList() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceList, + UsageLine: "list ", + Short: "lists revision of published repository", + Long: ` +Command lists sources of a published repository. + +Example: + + $ aptly publish source list wheezy +`, + Flag: *flag.NewFlagSet("aptly-publish-source-list", flag.ExitOnError), + } + cmd.Flag.Bool("json", false, "display record in JSON format") + cmd.Flag.String("prefix", ".", "publishing prefix in the form of [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_remove.go b/cmd/publish_source_remove.go new file mode 100644 index 00000000..d291bcf0 --- /dev/null +++ b/cmd/publish_source_remove.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/aptly-dev/aptly/deb" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPublishSourceRemove(cmd *commander.Command, args []string) error { + if len(args) < 1 { + cmd.Usage() + return commander.ErrCommandError + } + + distribution := args[0] + components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") + + if len(components) == 0 { + return fmt.Errorf("unable to remove: missing components, specify at least one component") + } + + prefix := context.Flags().Lookup("prefix").Value.String() + storage, prefix := deb.ParsePrefix(prefix) + + collectionFactory := context.NewCollectionFactory() + published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + return fmt.Errorf("unable to remove: %s", err) + } + + err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) + if err != nil { + return fmt.Errorf("unable to remove: %s", err) + } + + revision := published.ObtainRevision() + sources := revision.Sources + + for _, component := range components { + name, exists := sources[component] + if !exists { + return fmt.Errorf("unable to remove: component '%s' does not exist", component) + } + context.Progress().Printf("Removing component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind) + + delete(sources, component) + } + + err = collectionFactory.PublishedRepoCollection().Update(published) + if err != nil { + return fmt.Errorf("unable to save to DB: %s", err) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceRemove() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceRemove, + UsageLine: "remove [[:]] ", + Short: "remove source components from a published repo", + Long: ` +The command removes source components (snapshot / local repo) from a published repository. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +The flag -component is mandatory. Use a comma-separated list of components, if +multiple components should be removed, e.g.: + +Example: + + $ aptly publish source remove -component=contrib,non-free wheezy filesystem:symlink:debian +`, + Flag: *flag.NewFlagSet("aptly-publish-source-remove", flag.ExitOnError), + } + cmd.Flag.String("prefix", ".", "publishing prefix in the form of [:]") + cmd.Flag.String("component", "", "component names to remove (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_replace.go b/cmd/publish_source_replace.go new file mode 100644 index 00000000..17801e5a --- /dev/null +++ b/cmd/publish_source_replace.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/aptly-dev/aptly/deb" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPublishSourceReplace(cmd *commander.Command, args []string) error { + if len(args) < 2 { + cmd.Usage() + return commander.ErrCommandError + } + + distribution := args[0] + names := args[1:] + components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") + + if len(names) != len(components) { + return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names)) + } + + prefix := context.Flags().Lookup("prefix").Value.String() + storage, prefix := deb.ParsePrefix(prefix) + + collectionFactory := context.NewCollectionFactory() + published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + return fmt.Errorf("unable to add: %s", err) + } + + err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) + if err != nil { + return fmt.Errorf("unable to add: %s", err) + } + + revision := published.ObtainRevision() + sources := revision.Sources + context.Progress().Printf("Replacing source list...\n") + clear(sources) + + for i, component := range components { + name := names[i] + context.Progress().Printf("Adding component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind) + + sources[component] = name + } + + err = collectionFactory.PublishedRepoCollection().Update(published) + if err != nil { + return fmt.Errorf("unable to save to DB: %s", err) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceReplace() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceReplace, + UsageLine: "replace ", + Short: "replace the source components of a published repository", + Long: ` +The command replaces the source components of a snapshot or local repository to be published. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +The flag -component is mandatory. Use a comma-separated list of components, if +multiple components should be modified. The number of given components must be +equal to the number of given sources, e.g.: + + aptly publish source replace -component=main,contrib wheezy wheezy-main wheezy-contrib + +Example: + + $ aptly publish source replace -component=contrib wheezy ppa wheezy-contrib +`, + Flag: *flag.NewFlagSet("aptly-publish-source-add", flag.ExitOnError), + } + cmd.Flag.String("prefix", ".", "publishing prefix in the form of [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_update.go b/cmd/publish_source_update.go new file mode 100644 index 00000000..af207fb3 --- /dev/null +++ b/cmd/publish_source_update.go @@ -0,0 +1,91 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/aptly-dev/aptly/deb" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPublishSourceUpdate(cmd *commander.Command, args []string) error { + if len(args) < 2 { + cmd.Usage() + return commander.ErrCommandError + } + + distribution := args[0] + names := args[1:] + components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") + + if len(names) != len(components) { + return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names)) + } + + prefix := context.Flags().Lookup("prefix").Value.String() + storage, prefix := deb.ParsePrefix(prefix) + + collectionFactory := context.NewCollectionFactory() + published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + revision := published.ObtainRevision() + sources := revision.Sources + + for i, component := range components { + name := names[i] + _, exists := sources[component] + if !exists { + return fmt.Errorf("unable to update: component '%s' does not exist", component) + } + context.Progress().Printf("Updating component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind) + + sources[component] = name + } + + err = collectionFactory.PublishedRepoCollection().Update(published) + if err != nil { + return fmt.Errorf("unable to save to DB: %s", err) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceUpdate() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceUpdate, + UsageLine: "update ", + Short: "update the source components of a published repository", + Long: ` +The command updates the source components of a snapshot or local repository to be published. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +The flag -component is mandatory. Use a comma-separated list of components, if +multiple components should be modified. The number of given components must be +equal to the number of given sources, e.g.: + + aptly publish source update -component=main,contrib wheezy wheezy-main wheezy-contrib + +Example: + + $ aptly publish source update -component=contrib wheezy ppa wheezy-contrib +`, + Flag: *flag.NewFlagSet("aptly-publish-source-update", flag.ExitOnError), + } + cmd.Flag.String("prefix", ".", "publishing prefix in the form of [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_switch.go b/cmd/publish_switch.go index 0784fba3..f39269a1 100644 --- a/cmd/publish_switch.go +++ b/cmd/publish_switch.go @@ -11,7 +11,10 @@ import ( ) func aptlyPublishSwitch(cmd *commander.Command, args []string) error { - var err error + var ( + err error + names []string + ) components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") @@ -23,11 +26,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { distribution := args[0] param := "." - var ( - names []string - snapshot *deb.Snapshot - ) - if len(args) == len(components)+2 { param = args[1] names = args[2:] @@ -42,16 +40,16 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { collectionFactory := context.NewCollectionFactory() published, err = collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) if err != nil { - return fmt.Errorf("unable to update: %s", err) + return fmt.Errorf("unable to switch: %s", err) } if published.SourceKind != deb.SourceSnapshot { - return fmt.Errorf("unable to update: not a snapshot publish") + return fmt.Errorf("unable to switch: not a published snapshot repository") } err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) if err != nil { - return fmt.Errorf("unable to update: %s", err) + return fmt.Errorf("unable to switch: %s", err) } publishedComponents := published.Components() @@ -63,17 +61,18 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { return fmt.Errorf("mismatch in number of components (%d) and snapshots (%d)", len(components), len(names)) } + snapshotCollection := collectionFactory.SnapshotCollection() for i, component := range components { if !utils.StrSliceHasItem(publishedComponents, component) { - return fmt.Errorf("unable to switch: component %s is not in published repository", component) + return fmt.Errorf("unable to switch: component %s does not exist in published repository", component) } - snapshot, err = collectionFactory.SnapshotCollection().ByName(names[i]) + snapshot, err := snapshotCollection.ByName(names[i]) if err != nil { return fmt.Errorf("unable to switch: %s", err) } - err = collectionFactory.SnapshotCollection().LoadComplete(snapshot) + err = snapshotCollection.LoadComplete(snapshot) if err != nil { return fmt.Errorf("unable to switch: %s", err) } @@ -100,7 +99,11 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool) } - err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite) + if context.Flags().IsSet("multi-dist") { + published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool) + } + + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath()) if err != nil { return fmt.Errorf("unable to publish: %s", err) } @@ -112,14 +115,13 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool) if !skipCleanup { - err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components, - context.GetPublishedStorage(storage), collectionFactory, context.Progress()) + err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(context, published, components, collectionFactory, context.Progress()) if err != nil { - return fmt.Errorf("unable to update: %s", err) + return fmt.Errorf("unable to switch: %s", err) } } - context.Progress().Printf("\nPublish for snapshot %s has been successfully switched to new snapshot.\n", published.String()) + context.Progress().Printf("\nPublished %s repository %s has been successfully switched to new source.\n", published.SourceKind, published.String()) return err } @@ -127,15 +129,15 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { func makeCmdPublishSwitch() *commander.Command { cmd := &commander.Command{ Run: aptlyPublishSwitch, - UsageLine: "switch [[:]] ", - Short: "update published repository by switching to new snapshot", + UsageLine: "switch [[:]] ", + Short: "update published repository by switching to new source", Long: ` -Command switches in-place published snapshots with new snapshot contents. All +Command switches in-place published snapshots with new source contents. All publishing parameters are preserved (architecture list, distribution, component). For multiple component repositories, flag -component should be given with -list of components to update. Corresponding snapshots should be given in the +list of components to update. Corresponding sources should be given in the same order, e.g.: aptly publish switch -component=main,contrib wheezy wh-main wh-contrib @@ -161,6 +163,7 @@ This command would switch published repository (with one component) named ppa/wh cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component") + cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions") return cmd } diff --git a/cmd/publish_update.go b/cmd/publish_update.go index fcdea8ed..6ea638d4 100644 --- a/cmd/publish_update.go +++ b/cmd/publish_update.go @@ -31,18 +31,14 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } - if published.SourceKind != deb.SourceLocalRepo { - return fmt.Errorf("unable to update: not a local repository publish") - } - err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) if err != nil { return fmt.Errorf("unable to update: %s", err) } - components := published.Components() - for _, component := range components { - published.UpdateLocalRepo(component) + result, err := published.Update(collectionFactory, context.Progress()) + if err != nil { + return fmt.Errorf("unable to update: %s", err) } signer, err := getSigner(context.Flags()) @@ -64,7 +60,11 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error { published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool) } - err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite) + if context.Flags().IsSet("multi-dist") { + published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool) + } + + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath()) if err != nil { return fmt.Errorf("unable to publish: %s", err) } @@ -76,14 +76,15 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error { skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool) if !skipCleanup { - err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components, - context.GetPublishedStorage(storage), collectionFactory, context.Progress()) + cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources)) + cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...) + err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, context.Progress()) if err != nil { return fmt.Errorf("unable to update: %s", err) } } - context.Progress().Printf("\nPublish for local repo %s has been successfully updated.\n", published.String()) + context.Progress().Printf("\nPublished %s repository %s has been updated successfully.\n", published.SourceKind, published.String()) return err } @@ -92,15 +93,21 @@ func makeCmdPublishUpdate() *commander.Command { cmd := &commander.Command{ Run: aptlyPublishUpdate, UsageLine: "update [[:]]", - Short: "update published local repository", + Short: "update published repository", Long: ` -Command re-publishes (updates) published local repository. -and should be occupied with local repository published -using command aptly publish repo. Update happens in-place with -minimum possible downtime for published repository. +The command updates updates a published repository after applying pending changes to the sources. -For multiple component published repositories, all local repositories -are updated. +For published local repositories: + + * update to match local repository contents + +For published snapshots: + + * switch components to new snapshot + +The update happens in-place with minimum possible downtime for published repository. + +For multiple component published repositories, all local repositories are updated. Example: @@ -119,6 +126,7 @@ Example: cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component") + cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions") return cmd } diff --git a/cmd/repo_add.go b/cmd/repo_add.go index 8189e783..e7ccfac7 100644 --- a/cmd/repo_add.go +++ b/cmd/repo_add.go @@ -91,7 +91,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { func makeCmdRepoAdd() *commander.Command { cmd := &commander.Command{ Run: aptlyRepoAdd, - UsageLine: "add | ...", + UsageLine: "add (|)...", Short: "add packages to local repository", Long: ` Command adds packages to local repository from .deb, .udeb (binary packages) and .dsc (source packages) files. diff --git a/cmd/repo_include.go b/cmd/repo_include.go index b84b96a3..4aa26d41 100644 --- a/cmd/repo_include.go +++ b/cmd/repo_include.go @@ -29,7 +29,10 @@ func aptlyRepoInclude(cmd *commander.Command, args []string) error { forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool) acceptUnsigned := context.Flags().Lookup("accept-unsigned").Value.Get().(bool) - ignoreSignatures := context.Flags().Lookup("ignore-signatures").Value.Get().(bool) + ignoreSignatures := context.Config().GpgDisableVerify + if context.Flags().IsSet("ignore-signatures") { + ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool) + } noRemoveFiles := context.Flags().Lookup("no-remove-files").Value.Get().(bool) repoTemplateString := context.Flags().Lookup("repo").Value.Get().(string) collectionFactory := context.NewCollectionFactory() @@ -83,14 +86,14 @@ func aptlyRepoInclude(cmd *commander.Command, args []string) error { func makeCmdRepoInclude() *commander.Command { cmd := &commander.Command{ Run: aptlyRepoInclude, - UsageLine: "include | ...", + UsageLine: "include (|)...", Short: "add packages to local repositories based on .changes files", Long: ` Command include looks for .changes files in list of arguments or specified directories. Each .changes file is verified, parsed, referenced files are put into separate temporary directory and added into local repository. Successfully imported files are removed by default. -Additionally uploads could be restricted with file. Rules in this file control +Additionally uploads could be restricted with 'uploaders.json' file. Rules in this file control uploads based on GPG key ID of .changes file signature and queries on .changes file fields. Example: diff --git a/cmd/repo_list.go b/cmd/repo_list.go index 40dec6d4..9c4b0d47 100644 --- a/cmd/repo_list.go +++ b/cmd/repo_list.go @@ -24,7 +24,7 @@ func aptlyRepoList(cmd *commander.Command, args []string) error { return aptlyRepoListTxt(cmd, args) } -func aptlyRepoListTxt(cmd *commander.Command, args []string) error { +func aptlyRepoListTxt(cmd *commander.Command, _ []string) error { var err error raw := cmd.Flag.Lookup("raw").Value.Get().(bool) @@ -71,7 +71,7 @@ func aptlyRepoListTxt(cmd *commander.Command, args []string) error { return err } -func aptlyRepoListJSON(cmd *commander.Command, args []string) error { +func aptlyRepoListJSON(_ *commander.Command, _ []string) error { var err error repos := make([]*deb.LocalRepo, context.NewCollectionFactory().LocalRepoCollection().Len()) diff --git a/cmd/repo_move.go b/cmd/repo_move.go index 8be6698b..bd1447ce 100644 --- a/cmd/repo_move.go +++ b/cmd/repo_move.go @@ -110,13 +110,24 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error { queries := make([]deb.PackageQuery, len(args)-2) for i := 0; i < len(args)-2; i++ { - queries[i], err = query.Parse(args[i+2]) + value, err := GetStringOrFileContent(args[i+2]) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", args[i+2], err) + } + queries[i], err = query.Parse(value) if err != nil { return fmt.Errorf("unable to %s: %s", command, err) } } - toProcess, err := srcList.FilterWithProgress(queries, withDeps, dstList, context.DependencyOptions(), architecturesList, context.Progress()) + toProcess, err := srcList.Filter(deb.FilterOptions{ + Queries: queries, + WithDependencies: withDeps, + Source: dstList, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + Progress: context.Progress(), + }) if err != nil { return fmt.Errorf("unable to %s: %s", command, err) } @@ -179,6 +190,8 @@ func makeCmdRepoMove() *commander.Command { Command move moves packages matching from local repo to local repo . +Use '@file' to read package queries from file or '@-' for stdin. + Example: $ aptly repo move testing stable 'myapp (=0.1.12)' diff --git a/cmd/repo_remove.go b/cmd/repo_remove.go index 93e8535c..5341a4c3 100644 --- a/cmd/repo_remove.go +++ b/cmd/repo_remove.go @@ -38,14 +38,18 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error { queries := make([]deb.PackageQuery, len(args)-1) for i := 0; i < len(args)-1; i++ { - queries[i], err = query.Parse(args[i+1]) + value, err := GetStringOrFileContent(args[i+1]) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", args[i+1], err) + } + queries[i], err = query.Parse(value) if err != nil { return fmt.Errorf("unable to remove: %s", err) } } list.PrepareIndex() - toRemove, err := list.Filter(queries, false, nil, 0, nil) + toRemove, err := list.Filter(deb.FilterOptions{Queries: queries}) if err != nil { return fmt.Errorf("unable to remove: %s", err) } @@ -81,6 +85,8 @@ Commands removes packages matching from local repository snapshots, they can be removed completely (including files) by running 'aptly db cleanup'. +Use '@file' to read package queries from file or '@-' for stdin. + Example: $ aptly repo remove testing 'myapp (=0.1.12)' diff --git a/cmd/repo_show.go b/cmd/repo_show.go index 6a0be42d..a61a5f1f 100644 --- a/cmd/repo_show.go +++ b/cmd/repo_show.go @@ -25,7 +25,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error { return aptlyRepoShowTxt(cmd, args) } -func aptlyRepoShowTxt(cmd *commander.Command, args []string) error { +func aptlyRepoShowTxt(_ *commander.Command, args []string) error { var err error name := args[0] @@ -58,7 +58,7 @@ func aptlyRepoShowTxt(cmd *commander.Command, args []string) error { return err } -func aptlyRepoShowJSON(cmd *commander.Command, args []string) error { +func aptlyRepoShowJSON(_ *commander.Command, args []string) error { var err error name := args[0] diff --git a/cmd/serve.go b/cmd/serve.go index 9efaa772..be974b00 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -29,7 +29,7 @@ func aptlyServe(cmd *commander.Command, args []string) error { // anything else must fail. // E.g.: Running the service under a different user may lead to a rootDir // that exists but is not usable due to access permissions. - err = utils.DirIsAccessible(context.Config().RootDir) + err = utils.DirIsAccessible(context.Config().GetRootDir()) if err != nil { return err } diff --git a/cmd/snapshot_create.go b/cmd/snapshot_create.go index 000a78d9..74e3c966 100644 --- a/cmd/snapshot_create.go +++ b/cmd/snapshot_create.go @@ -84,7 +84,7 @@ func aptlySnapshotCreate(cmd *commander.Command, args []string) error { func makeCmdSnapshotCreate() *commander.Command { cmd := &commander.Command{ Run: aptlySnapshotCreate, - UsageLine: "create from mirror | from repo | empty", + UsageLine: "create (from mirror | from repo | empty)", Short: "creates snapshot of mirror (local repository) contents", Long: ` Command create from mirror makes persistent immutable snapshot of remote diff --git a/cmd/snapshot_filter.go b/cmd/snapshot_filter.go index b81a9cfc..d4e71dc4 100644 --- a/cmd/snapshot_filter.go +++ b/cmd/snapshot_filter.go @@ -60,14 +60,25 @@ func aptlySnapshotFilter(cmd *commander.Command, args []string) error { // Initial queries out of arguments queries := make([]deb.PackageQuery, len(args)-2) for i, arg := range args[2:] { - queries[i], err = query.Parse(arg) + value, err := GetStringOrFileContent(arg) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", arg, err) + } + queries[i], err = query.Parse(value) if err != nil { return fmt.Errorf("unable to parse query: %s", err) } } // Filter with dependencies as requested - result, err := packageList.FilterWithProgress(queries, withDeps, nil, context.DependencyOptions(), architecturesList, context.Progress()) + result, err := packageList.Filter(deb.FilterOptions{ + Queries: queries, + WithDependencies: withDeps, + Source: nil, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + Progress: context.Progress(), + }) if err != nil { return fmt.Errorf("unable to filter: %s", err) } @@ -96,6 +107,8 @@ Command filter does filtering in snapshot , producing another snapshot . Packages could be specified simply as 'package-name' or as package queries. +Use '@file' syntax to read package queries from file and '@-' to read from stdin. + Example: $ aptly snapshot filter wheezy-main wheezy-required 'Priority (required)' diff --git a/cmd/snapshot_list.go b/cmd/snapshot_list.go index b7836630..7435b187 100644 --- a/cmd/snapshot_list.go +++ b/cmd/snapshot_list.go @@ -23,7 +23,7 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error { return aptlySnapshotListTxt(cmd, args) } -func aptlySnapshotListTxt(cmd *commander.Command, args []string) error { +func aptlySnapshotListTxt(cmd *commander.Command, _ []string) error { var err error raw := cmd.Flag.Lookup("raw").Value.Get().(bool) @@ -59,7 +59,7 @@ func aptlySnapshotListTxt(cmd *commander.Command, args []string) error { return err } -func aptlySnapshotListJSON(cmd *commander.Command, args []string) error { +func aptlySnapshotListJSON(cmd *commander.Command, _ []string) error { var err error sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string) diff --git a/cmd/snapshot_pull.go b/cmd/snapshot_pull.go index 884b50ff..f73afab7 100644 --- a/cmd/snapshot_pull.go +++ b/cmd/snapshot_pull.go @@ -88,7 +88,11 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { // Initial queries out of arguments queries := make([]deb.PackageQuery, len(args)-3) for i, arg := range args[3:] { - queries[i], err = query.Parse(arg) + value, err := GetStringOrFileContent(arg) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", arg, err) + } + queries[i], err = query.Parse(value) if err != nil { return fmt.Errorf("unable to parse query: %s", err) } @@ -97,7 +101,14 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { } // Filter with dependencies as requested - result, err := sourcePackageList.FilterWithProgress(queries, !noDeps, packageList, context.DependencyOptions(), architecturesList, context.Progress()) + result, err := sourcePackageList.Filter(deb.FilterOptions{ + Queries: queries, + WithDependencies: !noDeps, + Source: packageList, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + Progress: context.Progress(), + }) if err != nil { return fmt.Errorf("unable to pull: %s", err) } @@ -112,7 +123,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { // If we haven't seen such name-architecture pair and were instructed to remove, remove it if !noRemove && !seen { // Remove all packages with the same name and architecture - pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true) + pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true, false) for _, p := range pS { packageList.Remove(p) context.Progress().ColoredPrintf("@r[-]@| %s removed", p) @@ -160,6 +171,8 @@ versions from following dependencies. New snapshot is created as a result of this process. Packages could be specified simply as 'package-name' or as package queries. +Use '@file' syntax to read package queries from file and '@-' to read from stdin. + Example: $ aptly snapshot pull wheezy-main wheezy-backports wheezy-new-xorg xorg-server-server diff --git a/cmd/snapshot_search.go b/cmd/snapshot_search.go index d771af7c..24da005d 100644 --- a/cmd/snapshot_search.go +++ b/cmd/snapshot_search.go @@ -78,7 +78,11 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error list.PrepareIndex() if len(args) == 2 { - q, err = query.Parse(args[1]) + value, err := GetStringOrFileContent(args[1]) + if err != nil { + return fmt.Errorf("unable to read package query from file %s: %w", args[1], err) + } + q, err = query.Parse(value) if err != nil { return fmt.Errorf("unable to search: %s", err) } @@ -103,8 +107,13 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error } } - result, err := list.FilterWithProgress([]deb.PackageQuery{q}, withDeps, - nil, context.DependencyOptions(), architecturesList, context.Progress()) + result, err := list.Filter(deb.FilterOptions{ + Queries: []deb.PackageQuery{q}, + WithDependencies: withDeps, + DependencyOptions: context.DependencyOptions(), + Architectures: architecturesList, + Progress: context.Progress(), + }) if err != nil { return fmt.Errorf("unable to search: %s", err) } @@ -129,6 +138,8 @@ Command search displays list of packages in snapshot that match package query If query is not specified, all the packages are displayed. +Use '@file' syntax to read package query from file and '@-' to read from stdin. + Example: $ aptly snapshot search wheezy-main '$Architecture (i386), Name (% *-dev)' diff --git a/cmd/snapshot_show.go b/cmd/snapshot_show.go index 98f15a9f..e03a49e5 100644 --- a/cmd/snapshot_show.go +++ b/cmd/snapshot_show.go @@ -25,7 +25,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error { return aptlySnapshotShowTxt(cmd, args) } -func aptlySnapshotShowTxt(cmd *commander.Command, args []string) error { +func aptlySnapshotShowTxt(_ *commander.Command, args []string) error { var err error name := args[0] collectionFactory := context.NewCollectionFactory() @@ -85,7 +85,7 @@ func aptlySnapshotShowTxt(cmd *commander.Command, args []string) error { return err } -func aptlySnapshotShowJSON(cmd *commander.Command, args []string) error { +func aptlySnapshotShowJSON(_ *commander.Command, args []string) error { var err error name := args[0] diff --git a/cmd/string_or_file_flag.go b/cmd/string_or_file_flag.go new file mode 100644 index 00000000..aee234f3 --- /dev/null +++ b/cmd/string_or_file_flag.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "io" + "os" + "strings" + + "github.com/smira/flag" +) + +// StringOrFileFlag is a custom flag type that can handle both string input and file input. +// If the input starts with '@', it is treated as a filename and the contents are read from the file. +// If the input is '@-', the contents are read from stdin. +type StringOrFileFlag struct { + value string +} + +func (s *StringOrFileFlag) String() string { + return s.value +} + +func (s *StringOrFileFlag) Set(value string) error { + var err error + s.value, err = GetStringOrFileContent(value) + return err +} + +func (s *StringOrFileFlag) Get() any { + return s.value +} + +func AddStringOrFileFlag(flagSet *flag.FlagSet, name string, value string, usage string) *StringOrFileFlag { + result := &StringOrFileFlag{value: value} + flagSet.Var(result, name, usage) + return result +} + +func GetStringOrFileContent(value string) (string, error) { + if !strings.HasPrefix(value, "@") { + return value, nil + } + + filename := strings.TrimPrefix(value, "@") + var data []byte + var err error + if filename == "-" { // Read from stdin + data, err = io.ReadAll(os.Stdin) + } else { + data, err = os.ReadFile(filename) + } + if err != nil { + return "", err + } + return string(data), nil +} diff --git a/cmd/task_run.go b/cmd/task_run.go index 8e18d94c..50519fe8 100644 --- a/cmd/task_run.go +++ b/cmd/task_run.go @@ -62,11 +62,10 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error { text, _ = reader.ReadString('\n') if text == "\n" { break - } else { - text = strings.TrimSpace(text) + "," - parsedArgs, _ := shellwords.Parse(text) - cmdArgs = append(cmdArgs, parsedArgs...) } + text = strings.TrimSpace(text) + "," + parsedArgs, _ := shellwords.Parse(text) + cmdArgs = append(cmdArgs, parsedArgs...) } if len(cmdArgs) == 0 { @@ -132,7 +131,7 @@ func formatCommands(args []string) [][]string { func makeCmdTaskRun() *commander.Command { cmd := &commander.Command{ Run: aptlyTaskRun, - UsageLine: "run -filename= | , , ...", + UsageLine: "run (-filename= | ...)", Short: "run aptly tasks", Long: ` Command helps organise multiple aptly commands in one single aptly task, running as single thread. diff --git a/codecov.yml b/codecov.yml index ec78c1ee..754a5cea 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,5 +3,5 @@ coverage: project: default: target: auto - threshold: 0% + threshold: 2% if_ci_failed: error diff --git a/completion.d/_aptly b/completion.d/_aptly index 505c0609..8e8d2bbd 100644 --- a/completion.d/_aptly +++ b/completion.d/_aptly @@ -457,6 +457,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could "-distribution=[distribution name to publish]:distribution:($dists)" "-label=[label to publish]:label: " "-suite=[suite to publish]:suite: " + "-codename=[codename to publish]:codename: " "-notautomatic=[set value for NotAutomatic field]:notautomatic: " "-origin=[origin name to publish]:origin: " ${components_options[@]} diff --git a/completion.d/aptly b/completion.d/aptly index afddd63d..3cb54ac5 100644 --- a/completion.d/aptly +++ b/completion.d/aptly @@ -1,5 +1,3 @@ -#!/bin/bash - # (The MIT License) # # Copyright (c) 2014 Andrey Smirnov @@ -56,12 +54,14 @@ _aptly() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" + prevprev="${COMP_WORDS[COMP_CWORD-2]}" commands="api config db graph mirror package publish repo serve snapshot task version" options="-architectures= -config= -db-open-attempts= -dep-follow-all-variants -dep-follow-recommends -dep-follow-source -dep-follow-suggests -dep-verbose-resolve -gpg-provider=" db_subcommands="cleanup recover" mirror_subcommands="create drop edit show list rename search update" - publish_subcommands="drop list repo snapshot switch update" + publish_subcommands="drop list repo snapshot switch update source" + publish_source_subcommands="drop list add remove update replace" snapshot_subcommands="create diff drop filter list merge pull rename search show verify" repo_subcommands="add copy create drop edit import include list move remove rename search show" package_subcommands="search show" @@ -150,6 +150,17 @@ _aptly() esac fi + case "$prevprev" in + "publish") + case "$prev" in + "source") + COMPREPLY=($(compgen -W "${publish_source_subcommands}" -- ${cur})) + return 0 + ;; + esac + ;; + esac + case "$cmd" in "mirror") case "$subcmd" in @@ -503,7 +514,7 @@ _aptly() "snapshot"|"repo") if [[ $numargs -eq 0 ]]; then if [[ "$cur" == -* ]]; then - COMPREPLY=($(compgen -W "-acquire-by-hash -batch -butautomaticupgrades= -component= -distribution= -force-overwrite -gpg-key= -keyring= -label= -suite= -notautomatic= -origin= -passphrase= -passphrase-file= -secret-keyring= -skip-contents -skip-bz2 -skip-signing" -- ${cur})) + COMPREPLY=($(compgen -W "-acquire-by-hash -batch -butautomaticupgrades= -component= -distribution= -force-overwrite -gpg-key= -keyring= -label= -suite= -codename= -notautomatic= -origin= -passphrase= -passphrase-file= -secret-keyring= -skip-contents -skip-bz2 -skip-signing -multi-dist" -- ${cur})) else if [[ "$subcmd" == "snapshot" ]]; then COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) @@ -577,6 +588,19 @@ _aptly() ;; esac ;; + "source") + case "$subcmd" in + "add") + return 0 + ;; + "list") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "-raw" -- ${cur})) + return 0 + fi + ;; + esac + ;; "package") case "$subcmd" in "search") diff --git a/console/progress.go b/console/progress.go index 3671fc3a..16191857 100644 --- a/console/progress.go +++ b/console/progress.go @@ -7,6 +7,7 @@ import ( "github.com/aptly-dev/aptly/aptly" "github.com/cheggaaa/pb" + "github.com/rs/zerolog/log" "github.com/wsxiaoys/terminal/color" ) @@ -34,6 +35,7 @@ type Progress struct { queue chan printTask bar *pb.ProgressBar barShown bool + worker ProgressWorker } // Check interface @@ -42,16 +44,19 @@ var ( ) // NewProgress creates new progress instance -func NewProgress() *Progress { - return &Progress{ +func NewProgress(structuredLogging bool) *Progress { + p := &Progress{ stopped: make(chan bool), queue: make(chan printTask, 100), } + + p.worker = progressWorkerFactroy(structuredLogging, p) + return p } // Start makes progress start its work func (p *Progress) Start() { - go p.worker() + go p.worker.run() } // Shutdown shuts down progress display @@ -69,7 +74,7 @@ func (p *Progress) Flush() { } // InitBar starts progressbar for count bytes or count items -func (p *Progress) InitBar(count int64, isBytes bool, barType aptly.BarType) { +func (p *Progress) InitBar(count int64, isBytes bool, _ aptly.BarType) { if p.bar != nil { panic("bar already initialized") } @@ -173,42 +178,94 @@ func (p *Progress) ColoredPrintf(msg string, a ...interface{}) { } } -func (p *Progress) worker() { +type ProgressWorker interface { + run() +} + +func progressWorkerFactroy(structuredLogging bool, progress *Progress) ProgressWorker { + if structuredLogging { + worker := loggerProgressWorker{progress: progress} + return &worker + } + + worker := standardProgressWorker{progress: progress} + return &worker +} + +type standardProgressWorker struct { + progress *Progress +} + +func (w *standardProgressWorker) run() { hasBar := false for { - task := <-p.queue + task := <-w.progress.queue switch task.code { case codeBarEnabled: hasBar = true case codeBarDisabled: hasBar = false case codePrint: - if p.barShown { + if w.progress.barShown { fmt.Print("\r\033[2K") - p.barShown = false + w.progress.barShown = false } fmt.Print(task.message) case codePrintStdErr: - if p.barShown { + if w.progress.barShown { fmt.Print("\r\033[2K") - p.barShown = false + w.progress.barShown = false } fmt.Fprint(os.Stderr, task.message) case codeProgress: if hasBar { fmt.Print("\r" + task.message) - p.barShown = true + w.progress.barShown = true } case codeHideProgress: - if p.barShown { + if w.progress.barShown { fmt.Print("\r\033[2K") - p.barShown = false + w.progress.barShown = false } case codeFlush: task.reply <- true case codeStop: - p.stopped <- true + w.progress.stopped <- true + return + } + } +} + +type loggerProgressWorker struct { + progress *Progress +} + +func (w *loggerProgressWorker) run() { + hasBar := false + + for { + task := <-w.progress.queue + switch task.code { + case codeBarEnabled: + hasBar = true + case codeBarDisabled: + hasBar = false + case codePrint, codePrintStdErr: + log.Info().Msg(strings.TrimSuffix(task.message, "\n")) + case codeProgress: + if hasBar { + log.Info().Msg(strings.TrimSuffix(task.message, "\n")) + w.progress.barShown = true + } + case codeHideProgress: + if w.progress.barShown { + w.progress.barShown = false + } + case codeFlush: + task.reply <- true + case codeStop: + w.progress.stopped <- true return } } diff --git a/console/progress_test.go b/console/progress_test.go new file mode 100644 index 00000000..2b412eca --- /dev/null +++ b/console/progress_test.go @@ -0,0 +1,24 @@ +package console + +import ( + "fmt" + "testing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + TestingT(t) +} + +type ProgressSuite struct {} + +var _ = Suite(&ProgressSuite{}) + +func (s *ProgressSuite) TestNewProgress(c *C) { + p := NewProgress(false) + c.Check(fmt.Sprintf("%T", p.worker), Equals, fmt.Sprintf("%T", &standardProgressWorker{})) + + p = NewProgress(true) + c.Check(fmt.Sprintf("%T", p.worker), Equals, fmt.Sprintf("%T", &loggerProgressWorker{})) +} diff --git a/context/context.go b/context/context.go index 8ee3ebf9..5df5bbfd 100644 --- a/context/context.go +++ b/context/context.go @@ -12,12 +12,14 @@ import ( "runtime/pprof" "strings" "sync" + "syscall" "time" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/azure" "github.com/aptly-dev/aptly/console" "github.com/aptly-dev/aptly/database" + "github.com/aptly-dev/aptly/database/etcddb" "github.com/aptly-dev/aptly/database/goleveldb" "github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/files" @@ -48,6 +50,7 @@ type AptlyContext struct { publishedStorages map[string]aptly.PublishedStorage dependencyOptions int architecturesList []string + structuredLogging bool // Debug features fileCPUProfile *os.File fileMemProfile *os.File @@ -93,13 +96,14 @@ func (context *AptlyContext) config() *utils.ConfigStructure { Fatal(err) } } else { - configLocations := []string{ - filepath.Join(os.Getenv("HOME"), ".aptly.conf"), - "/etc/aptly.conf", - } + homeLocation := filepath.Join(os.Getenv("HOME"), ".aptly.conf") + configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"} for _, configLocation := range configLocations { err = utils.LoadConfig(configLocation, &utils.Config) + if os.IsPermission(err) || os.IsNotExist(err) { + continue + } if err == nil { break } @@ -109,11 +113,13 @@ func (context *AptlyContext) config() *utils.ConfigStructure { } if err != nil { - fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", configLocations[0]) + fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", homeLocation) - // as this is fresh aptly installation, we don't need to support legacy pool locations - utils.Config.SkipLegacyPool = true - utils.SaveConfig(configLocations[0], &utils.Config) + utils.SaveConfigRaw(homeLocation, aptly.AptlyConf) + err = utils.LoadConfig(homeLocation, &utils.Config) + if err != nil { + Fatal(fmt.Errorf("error loading config file %s: %s", homeLocation, err)) + } } } @@ -195,7 +201,7 @@ func (context *AptlyContext) Progress() aptly.Progress { func (context *AptlyContext) _progress() aptly.Progress { if context.progress == nil { - context.progress = console.NewProgress() + context.progress = console.NewProgress(context.structuredLogging) context.progress.Start() } @@ -272,7 +278,7 @@ func (context *AptlyContext) DBPath() string { // DBPath builds path to database func (context *AptlyContext) dbPath() string { - return filepath.Join(context.config().RootDir, "db") + return filepath.Join(context.config().GetRootDir(), "db") } // Database opens and returns current instance of database @@ -286,8 +292,18 @@ func (context *AptlyContext) Database() (database.Storage, error) { func (context *AptlyContext) _database() (database.Storage, error) { if context.database == nil { var err error - - context.database, err = goleveldb.NewDB(context.dbPath()) + switch context.config().DatabaseBackend.Type { + case "leveldb": + dbPath := filepath.Join(context.config().GetRootDir(), "db") + if len(context.config().DatabaseBackend.DbPath) != 0 { + dbPath = context.config().DatabaseBackend.DbPath + } + context.database, err = goleveldb.NewDB(dbPath) + case "etcd": + context.database, err = etcddb.NewDB(context.config().DatabaseBackend.URL) + default: + context.database, err = goleveldb.NewDB(context.dbPath()) + } if err != nil { return nil, fmt.Errorf("can't instantiate database: %s", err) } @@ -344,10 +360,7 @@ func (context *AptlyContext) ReOpenDatabase() error { // NewCollectionFactory builds factory producing all kinds of collections func (context *AptlyContext) NewCollectionFactory() *deb.CollectionFactory { - context.Lock() - defer context.Unlock() - - db, err := context._database() + db, err := context.Database() if err != nil { Fatal(err) } @@ -360,7 +373,26 @@ func (context *AptlyContext) PackagePool() aptly.PackagePool { defer context.Unlock() if context.packagePool == nil { - context.packagePool = files.NewPackagePool(context.config().RootDir, !context.config().SkipLegacyPool) + storageConfig := context.config().PackagePoolStorage + if storageConfig.Azure != nil { + var err error + context.packagePool, err = azure.NewPackagePool( + storageConfig.Azure.AccountName, + storageConfig.Azure.AccountKey, + storageConfig.Azure.Container, + storageConfig.Azure.Prefix, + storageConfig.Azure.Endpoint) + if err != nil { + Fatal(err) + } + } else { + poolRoot := context.config().PackagePoolStorage.Local.Path + if poolRoot == "" { + poolRoot = filepath.Join(context.config().GetRootDir(), "pool") + } + + context.packagePool = files.NewPackagePool(poolRoot, !context.config().SkipLegacyPool) + } } return context.packagePool @@ -374,7 +406,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto publishedStorage, ok := context.publishedStorages[name] if !ok { if name == "" { - publishedStorage = files.NewPublishedStorage(filepath.Join(context.config().RootDir, "public"), "hardlink", "") + publishedStorage = files.NewPublishedStorage(filepath.Join(context.config().GetRootDir(), "public"), "hardlink", "") } else if strings.HasPrefix(name, "filesystem:") { params, ok := context.config().FileSystemPublishRoots[name[11:]] if !ok { @@ -393,7 +425,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto params.AccessKeyID, params.SecretAccessKey, params.SessionToken, params.Region, params.Endpoint, params.Bucket, params.ACL, params.Prefix, params.StorageClass, params.EncryptionMethod, params.PlusWorkaround, params.DisableMultiDel, - params.ForceSigV2, params.Debug) + params.ForceSigV2, params.ForceVirtualHostedStyle, params.Debug) if err != nil { Fatal(err) } @@ -432,7 +464,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto // UploadPath builds path to upload storage func (context *AptlyContext) UploadPath() string { - return filepath.Join(context.Config().RootDir, "upload") + return filepath.Join(context.Config().GetRootDir(), "upload") } func (context *AptlyContext) pgpProvider() string { @@ -456,7 +488,7 @@ func (context *AptlyContext) pgpProvider() string { return provider } -func (context *AptlyContext) getGPGFinder(provider string) pgp.GPGFinder { +func (context *AptlyContext) getGPGFinder() pgp.GPGFinder { switch context.pgpProvider() { case "gpg1": return pgp.GPG1Finder() @@ -479,7 +511,7 @@ func (context *AptlyContext) GetSigner() pgp.Signer { return &pgp.GoSigner{} } - return pgp.NewGpgSigner(context.getGPGFinder(provider)) + return pgp.NewGpgSigner(context.getGPGFinder()) } // GetVerifier returns Verifier with respect to provider @@ -492,7 +524,12 @@ func (context *AptlyContext) GetVerifier() pgp.Verifier { return &pgp.GoVerifier{} } - return pgp.NewGpgVerifier(context.getGPGFinder(provider)) + return pgp.NewGpgVerifier(context.getGPGFinder()) +} + +// SkelPath builds the local skeleton folder +func (context *AptlyContext) SkelPath() string { + return filepath.Join(context.config().GetRootDir(), "skel") } // UpdateFlags sets internal copy of flags in the context @@ -526,7 +563,7 @@ func (context *AptlyContext) GoContextHandleSignals() { // Catch ^C sigch := make(chan os.Signal, 1) - signal.Notify(sigch, os.Interrupt) + signal.Notify(sigch, syscall.SIGINT, syscall.SIGTERM) var cancel gocontext.CancelFunc @@ -540,6 +577,11 @@ func (context *AptlyContext) GoContextHandleSignals() { }() } +// StructuredLogging allows to set the structuredLogging flag +func (context *AptlyContext) StructuredLogging(structuredLogging bool) { + context.structuredLogging = structuredLogging +} + // Shutdown shuts context down func (context *AptlyContext) Shutdown() { context.Lock() @@ -561,6 +603,9 @@ func (context *AptlyContext) Shutdown() { context.fileMemProfile = nil } } + if context.taskList != nil { + context.taskList.Stop() + } if context.database != nil { context.database.Close() context.database = nil diff --git a/context/context_test.go b/context/context_test.go index 0a48f920..16ecbb2b 100644 --- a/context/context_test.go +++ b/context/context_test.go @@ -1,6 +1,8 @@ package context import ( + "fmt" + "os" "reflect" "testing" @@ -82,5 +84,6 @@ func (s *AptlyContextSuite) TestGetPublishedStorageBadFS(c *C) { // storage never exists. c.Assert(func() { s.context.GetPublishedStorage("filesystem:fuji") }, FatalErrorPanicMatches, - &FatalError{ReturnCode: 1, Message: "published local storage fuji not configured"}) + &FatalError{ReturnCode: 1, Message: fmt.Sprintf("error loading config file %s/.aptly.conf: invalid yaml (EOF) or json (EOF)", + os.Getenv("HOME"))}) } diff --git a/database/etcddb/batch.go b/database/etcddb/batch.go new file mode 100644 index 00000000..24b83de9 --- /dev/null +++ b/database/etcddb/batch.go @@ -0,0 +1,53 @@ +package etcddb + +import ( + "github.com/aptly-dev/aptly/database" + clientv3 "go.etcd.io/etcd/client/v3" +) + +type EtcDBatch struct { + s *EtcDStorage + ops []clientv3.Op +} + +type WriteOptions struct { + NoWriteMerge bool + Sync bool +} + +func (b *EtcDBatch) Put(key []byte, value []byte) (err error) { + b.ops = append(b.ops, clientv3.OpPut(string(key), string(value))) + return +} + +func (b *EtcDBatch) Delete(key []byte) (err error) { + b.ops = append(b.ops, clientv3.OpDelete(string(key))) + return +} + +func (b *EtcDBatch) Write() (err error) { + kv := clientv3.NewKV(b.s.db) + + batchSize := 128 + for i := 0; i < len(b.ops); i += batchSize { + txn := kv.Txn(Ctx) + end := i + batchSize + if end > len(b.ops) { + end = len(b.ops) + } + + batch := b.ops[i:end] + txn.Then(batch...) + _, err = txn.Commit() + if err != nil { + panic(err) + } + } + + return +} + +// batch should implement database.Batch +var ( + _ database.Batch = &EtcDBatch{} +) diff --git a/database/etcddb/database.go b/database/etcddb/database.go new file mode 100644 index 00000000..37a222e6 --- /dev/null +++ b/database/etcddb/database.go @@ -0,0 +1,32 @@ +package etcddb + +import ( + "context" + "time" + + "github.com/aptly-dev/aptly/database" + clientv3 "go.etcd.io/etcd/client/v3" +) + +var Ctx = context.TODO() + +func internalOpen(url string) (cli *clientv3.Client, err error) { + cfg := clientv3.Config{ + Endpoints: []string{url}, + DialTimeout: 30 * time.Second, + MaxCallSendMsgSize: 2147483647, // (2048 * 1024 * 1024) - 1 + MaxCallRecvMsgSize: 2147483647, + DialKeepAliveTimeout: 7200 * time.Second, + } + + cli, err = clientv3.New(cfg) + return +} + +func NewDB(url string) (database.Storage, error) { + cli, err := internalOpen(url) + if err != nil { + return nil, err + } + return &EtcDStorage{url, cli, ""}, nil +} diff --git a/database/etcddb/database_test.go b/database/etcddb/database_test.go new file mode 100644 index 00000000..b31e5599 --- /dev/null +++ b/database/etcddb/database_test.go @@ -0,0 +1,158 @@ +package etcddb_test + +import ( + "testing" + + "github.com/aptly-dev/aptly/database" + "github.com/aptly-dev/aptly/database/etcddb" + . "gopkg.in/check.v1" +) + +// Launch gocheck tests +func Test(t *testing.T) { + TestingT(t) +} + +type EtcDDBSuite struct { + url string + db database.Storage +} + +var _ = Suite(&EtcDDBSuite{}) + +func (s *EtcDDBSuite) SetUpTest(c *C) { + var err error + s.db, err = etcddb.NewDB("127.0.0.1:2379") + c.Assert(err, IsNil) +} + +func (s *EtcDDBSuite) TestSetUpTest(c *C) { + var err error + s.db, err = etcddb.NewDB("127.0.0.1:2379") + c.Assert(err, IsNil) +} + +func (s *EtcDDBSuite) TestGetPut(c *C) { + var ( + key = []byte("key") + value = []byte("value") + ) + var err error + + err = s.db.Put(key, value) + c.Assert(err, IsNil) + + result, err := s.db.Get(key) + c.Assert(err, IsNil) + c.Assert(result, DeepEquals, value) +} + +func (s *EtcDDBSuite) TestDelete(c *C) { + var ( + key = []byte("key") + value = []byte("value") + ) + + err := s.db.Put(key, value) + c.Assert(err, IsNil) + + _, err = s.db.Get(key) + c.Assert(err, IsNil) + + err = s.db.Delete(key) + c.Assert(err, IsNil) + +} + +func (s *EtcDDBSuite) TestByPrefix(c *C) { + //c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{}) + + s.db.Put([]byte{0x80, 0x01}, []byte{0x01}) + s.db.Put([]byte{0x80, 0x03}, []byte{0x03}) + s.db.Put([]byte{0x80, 0x02}, []byte{0x02}) + c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}}) + c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}) + + s.db.Put([]byte{0x90, 0x01}, []byte{0x04}) + c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}}) + c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}) + + s.db.Put([]byte{0x00, 0x01}, []byte{0x05}) + c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}}) + c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}) + + keys := [][]byte{} + values := [][]byte{} + + c.Check(s.db.ProcessByPrefix([]byte{0x80}, func(k, v []byte) error { + keys = append(keys, append([]byte(nil), k...)) + values = append(values, append([]byte(nil), v...)) + return nil + }), IsNil) + + c.Check(values, DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}}) + c.Check(keys, DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}) + + c.Check(s.db.ProcessByPrefix([]byte{0x80}, func(k, v []byte) error { + return database.ErrNotFound + }), Equals, database.ErrNotFound) + + c.Check(s.db.ProcessByPrefix([]byte{0xa0}, func(k, v []byte) error { + return database.ErrNotFound + }), IsNil) + + c.Check(s.db.FetchByPrefix([]byte{0xa0}), DeepEquals, [][]byte{}) + c.Check(s.db.KeysByPrefix([]byte{0xa0}), DeepEquals, [][]byte{}) +} + +func (s *EtcDDBSuite) TestHasPrefix(c *C) { + //c.Check(s.db.HasPrefix([]byte(nil)), Equals, false) + //c.Check(s.db.HasPrefix([]byte{0x80}), Equals, false) + + s.db.Put([]byte{0x80, 0x01}, []byte{0x01}) + + c.Check(s.db.HasPrefix([]byte(nil)), Equals, true) + c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true) + c.Check(s.db.HasPrefix([]byte{0x79}), Equals, false) +} + +func (s *EtcDDBSuite) TestTransactionCommit(c *C) { + var ( + key = []byte("key") + key2 = []byte("key2") + value = []byte("value") + value2 = []byte("value2") + ) + transaction, err := s.db.OpenTransaction() + + err = s.db.Put(key, value) + c.Assert(err, IsNil) + + c.Assert(err, IsNil) + transaction.Put(key2, value2) + v, err := s.db.Get(key) + c.Check(v, DeepEquals, value) + err = transaction.Delete(key) + c.Assert(err, IsNil) + + _, err = transaction.Get(key2) + c.Assert(err, IsNil) + + v2, err := transaction.Get(key2) + c.Check(err, IsNil) + c.Check(v2, DeepEquals, value2) + + _, err = transaction.Get(key) + c.Assert(err, IsNil) + + err = transaction.Commit() + c.Check(err, IsNil) + + v2, err = transaction.Get(key2) + c.Check(err, IsNil) + c.Check(v2, DeepEquals, value2) + + _, err = transaction.Get(key) + c.Assert(err, NotNil) +} + diff --git a/database/etcddb/storage.go b/database/etcddb/storage.go new file mode 100644 index 00000000..efc4cf64 --- /dev/null +++ b/database/etcddb/storage.go @@ -0,0 +1,202 @@ +package etcddb + +import ( + "github.com/aptly-dev/aptly/database" + "github.com/pborman/uuid" + clientv3 "go.etcd.io/etcd/client/v3" + + "fmt" +) + +type EtcDStorage struct { + url string + db *clientv3.Client + tmpPrefix string // prefix for temporary DBs +} + +// CreateTemporary creates new DB of the same type in temp dir +func (s *EtcDStorage) CreateTemporary() (database.Storage, error) { + tmp := uuid.NewRandom().String() + return &EtcDStorage{ + url: s.url, + db: s.db, + tmpPrefix: tmp, + }, nil +} + +func (s *EtcDStorage) applyPrefix(key []byte) []byte { + if len(s.tmpPrefix) != 0 { + return append([]byte(s.tmpPrefix+"/"), key...) + } + return key +} + +// Get key value from etcd +func (s *EtcDStorage) Get(key []byte) (value []byte, err error) { + realKey := s.applyPrefix(key) + getResp, err := s.db.Get(Ctx, string(realKey)) + if err != nil { + return + } + for _, kv := range getResp.Kvs { + value = kv.Value + break + } + if len(value) == 0 { + err = database.ErrNotFound + return + } + return +} + +// Put saves key to etcd, if key has the same value in DB already, it is not saved +func (s *EtcDStorage) Put(key []byte, value []byte) (err error) { + realKey := s.applyPrefix(key) + _, err = s.db.Put(Ctx, string(realKey), string(value)) + if err != nil { + return + } + return +} + +// Delete removes key from etcd +func (s *EtcDStorage) Delete(key []byte) (err error) { + realKey := s.applyPrefix(key) + _, err = s.db.Delete(Ctx, string(realKey)) + if err != nil { + return + } + return +} + +// KeysByPrefix returns all keys that start with prefix +func (s *EtcDStorage) KeysByPrefix(prefix []byte) [][]byte { + realPrefix := s.applyPrefix(prefix) + result := make([][]byte, 0, 20) + getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + if err != nil { + return nil + } + for _, ev := range getResp.Kvs { + key := ev.Key + keyc := make([]byte, len(key)) + copy(keyc, key) + result = append(result, key) + } + return result +} + +// FetchByPrefix returns all values with keys that start with prefix +func (s *EtcDStorage) FetchByPrefix(prefix []byte) [][]byte { + realPrefix := s.applyPrefix(prefix) + result := make([][]byte, 0, 20) + getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + if err != nil { + return nil + } + for _, kv := range getResp.Kvs { + valc := make([]byte, len(kv.Value)) + copy(valc, kv.Value) + result = append(result, kv.Value) + } + + return result +} + +// HasPrefix checks whether it can find any key with given prefix and returns true if one exists +func (s *EtcDStorage) HasPrefix(prefix []byte) bool { + realPrefix := s.applyPrefix(prefix) + getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + if err != nil { + return false + } + return getResp.Count > 0 +} + +// ProcessByPrefix iterates through all entries where key starts with prefix and calls +// StorageProcessor on key value pair +func (s *EtcDStorage) ProcessByPrefix(prefix []byte, proc database.StorageProcessor) error { + realPrefix := s.applyPrefix(prefix) + getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + if err != nil { + return err + } + + for _, kv := range getResp.Kvs { + err := proc(kv.Key, kv.Value) + if err != nil { + return err + } + } + return nil +} + +// Close finishes etcd connect +func (s *EtcDStorage) Close() error { + // do not close temporary db + if len(s.tmpPrefix) != 0 { + return nil + } + if s.db == nil { + return nil + } + err := s.db.Close() + s.db = nil + return err +} + +// Reopen tries to open (re-open) the database +func (s *EtcDStorage) Open() error { + if s.db != nil { + return nil + } + var err error + s.db, err = internalOpen(s.url) + return err +} + +// CreateBatch creates a Batch object +func (s *EtcDStorage) CreateBatch() database.Batch { + if s.db == nil { + return nil + } + return &EtcDBatch{ + s: s, + } +} + +// OpenTransaction creates new transaction. +func (s *EtcDStorage) OpenTransaction() (database.Transaction, error) { + tmpdb, err := s.CreateTemporary() + if err != nil { + return nil, err + } + return &transaction{s: s, tmpdb: tmpdb}, nil +} + +// CompactDB does nothing for etcd +func (s *EtcDStorage) CompactDB() error { + return nil +} + +// Drop removes only temporary DBs with etcd (i.e. remove all prefixed keys) +func (s *EtcDStorage) Drop() error { + if len(s.tmpPrefix) != 0 { + getResp, err := s.db.Get(Ctx, s.tmpPrefix, clientv3.WithPrefix()) + if err != nil { + return nil + } + for _, kv := range getResp.Kvs { + _, err = s.db.Delete(Ctx, string(kv.Key)) + if err != nil { + return fmt.Errorf("cannot delete tempdb entry: %s", kv.Key) + } + } + } + return nil +} + +// Check interface +var ( + _ database.Storage = &EtcDStorage{} +) diff --git a/database/etcddb/transaction.go b/database/etcddb/transaction.go new file mode 100644 index 00000000..01f54dad --- /dev/null +++ b/database/etcddb/transaction.go @@ -0,0 +1,75 @@ +package etcddb + +import ( + "github.com/aptly-dev/aptly/database" + clientv3 "go.etcd.io/etcd/client/v3" +) + +type transaction struct { + s *EtcDStorage + tmpdb database.Storage + ops []clientv3.Op +} + +// Get implements database.Reader interface. +func (t *transaction) Get(key []byte) (value []byte, err error) { + value, err = t.tmpdb.Get(key) + // if not found, search main db + if err != nil { + value, err = t.s.Get(key) + } + return +} + +// Put implements database.Writer interface. +func (t *transaction) Put(key, value []byte) (err error) { + err = t.tmpdb.Put(key, value) + if err != nil { + return + } + t.ops = append(t.ops, clientv3.OpPut(string(key), string(value))) + return +} + +// Delete implements database.Writer interface. +func (t *transaction) Delete(key []byte) (err error) { + err = t.tmpdb.Delete(key) + if err != nil { + return + } + t.ops = append(t.ops, clientv3.OpDelete(string(key))) + return +} + +func (t *transaction) Commit() (err error) { + kv := clientv3.NewKV(t.s.db) + + batchSize := 128 + for i := 0; i < len(t.ops); i += batchSize { + txn := kv.Txn(Ctx) + end := i + batchSize + if end > len(t.ops) { + end = len(t.ops) + } + + batch := t.ops[i:end] + txn.Then(batch...) + _, err = txn.Commit() + if err != nil { + panic(err) + } + } + t.ops = []clientv3.Op{} + + return +} + +// Discard is safe to call after Commit(), it would be no-op +func (t *transaction) Discard() { + t.ops = []clientv3.Op{} + t.tmpdb.Drop() + return +} + +// transaction should implement database.Transaction +var _ database.Transaction = &transaction{} diff --git a/database/goleveldb/storage.go b/database/goleveldb/storage.go index a95c5680..37acf3d8 100644 --- a/database/goleveldb/storage.go +++ b/database/goleveldb/storage.go @@ -3,7 +3,6 @@ package goleveldb import ( "bytes" "errors" - "io/ioutil" "os" "github.com/syndtr/goleveldb/leveldb" @@ -19,7 +18,7 @@ type storage struct { // CreateTemporary creates new DB of the same type in temp dir func (s *storage) CreateTemporary() (database.Storage, error) { - tempdir, err := ioutil.TempDir("", "aptly") + tempdir, err := os.MkdirTemp("", "aptly") if err != nil { return nil, err } diff --git a/deb/changes.go b/deb/changes.go index 3e2c6914..c264986a 100644 --- a/deb/changes.go +++ b/deb/changes.go @@ -4,16 +4,17 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "path/filepath" "sort" "strings" + "sync" "text/template" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/pgp" "github.com/aptly-dev/aptly/utils" + "github.com/saracen/walker" ) // Changes is a result of .changes file parsing @@ -39,7 +40,7 @@ func NewChanges(path string) (*Changes, error) { ChangesName: filepath.Base(path), } - c.TempDir, err = ioutil.TempDir(os.TempDir(), "aptly") + c.TempDir, err = os.MkdirTemp(os.TempDir(), "aptly") if err != nil { return nil, err } @@ -221,12 +222,12 @@ func (c *Changes) GetField(field string) string { } // MatchesDependency implements PackageLike interface -func (c *Changes) MatchesDependency(d Dependency) bool { +func (c *Changes) MatchesDependency(_ Dependency) bool { return false } // MatchesArchitecture implements PackageLike interface -func (c *Changes) MatchesArchitecture(arch string) bool { +func (c *Changes) MatchesArchitecture(_ string) bool { return false } @@ -248,6 +249,8 @@ func (c *Changes) GetArchitecture() string { // CollectChangesFiles walks filesystem collecting all .changes files func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (changesFiles, failedFiles []string) { + changesFilesLock := &sync.Mutex{} + for _, location := range locations { info, err2 := os.Stat(location) if err2 != nil { @@ -256,15 +259,14 @@ func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (cha continue } if info.IsDir() { - err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error { - if err3 != nil { - return err3 - } + err2 = walker.Walk(location, func(path string, info os.FileInfo) error { if info.IsDir() { return nil } if strings.HasSuffix(info.Name(), ".changes") { + changesFilesLock.Lock() + defer changesFilesLock.Unlock() changesFiles = append(changesFiles, path) } diff --git a/deb/changes_test.go b/deb/changes_test.go index 64b962b7..b7dc4d95 100644 --- a/deb/changes_test.go +++ b/deb/changes_test.go @@ -45,7 +45,7 @@ func (s *ChangesSuite) SetUpTest(c *C) { s.checksumStorage = files.NewMockChecksumStorage() s.packagePool = files.NewPackagePool(s.Dir, false) - s.progress = console.NewProgress() + s.progress = console.NewProgress(false) s.progress.Start() } diff --git a/deb/contents.go b/deb/contents.go index 9f694354..cc8f8cef 100644 --- a/deb/contents.go +++ b/deb/contents.go @@ -64,7 +64,7 @@ func (index *ContentsIndex) WriteTo(w io.Writer) (int64, error) { currentPkgs [][]byte ) - err = index.db.ProcessByPrefix(index.prefix, func(key []byte, value []byte) error { + err = index.db.ProcessByPrefix(index.prefix, func(key []byte, _ []byte) error { // cut prefix key = key[prefixLen:] diff --git a/deb/format.go b/deb/format.go index 47ea776e..95febe35 100644 --- a/deb/format.go +++ b/deb/format.go @@ -136,7 +136,7 @@ func isMultilineField(field string, isRelease bool) bool { // Write single field from Stanza to writer. // -//nolint: interfacer +// nolint: interfacer func writeField(w *bufio.Writer, field, value string, isRelease bool) (err error) { if !isMultilineField(field, isRelease) { _, err = w.WriteString(field + ": " + value + "\n") diff --git a/deb/graph.go b/deb/graph.go index 4c45ca9f..16a7ce85 100644 --- a/deb/graph.go +++ b/deb/graph.go @@ -119,8 +119,8 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz "shape": "Mrecord", "style": "filled", "fillcolor": "darkolivegreen1", - "label": fmt.Sprintf("%sPublished %s/%s|comp: %s|arch: %s%s", labelStart, - repo.Prefix, repo.Distribution, strings.Join(repo.Components(), " "), + "label": fmt.Sprintf("%sPublished %s|comp: %s|arch: %s%s", labelStart, + repo.GetPath(), strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", "), labelEnd), }) diff --git a/deb/import.go b/deb/import.go index 53322f8d..99d52be8 100644 --- a/deb/import.go +++ b/deb/import.go @@ -5,14 +5,19 @@ import ( "path/filepath" "sort" "strings" + "sync" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/pgp" "github.com/aptly-dev/aptly/utils" + "github.com/saracen/walker" ) // CollectPackageFiles walks filesystem collecting all candidates for package files func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (packageFiles, otherFiles, failedFiles []string) { + packageFilesLock := &sync.Mutex{} + otherFilesLock := &sync.Mutex{} + for _, location := range locations { info, err2 := os.Stat(location) if err2 != nil { @@ -21,18 +26,19 @@ func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (pac continue } if info.IsDir() { - err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error { - if err3 != nil { - return err3 - } + err2 = walker.Walk(location, func(path string, info os.FileInfo) error { if info.IsDir() { return nil } if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || strings.HasSuffix(info.Name(), ".dsc") || strings.HasSuffix(info.Name(), ".ddeb") { + packageFilesLock.Lock() + defer packageFilesLock.Unlock() packageFiles = append(packageFiles, path) } else if strings.HasSuffix(info.Name(), ".buildinfo") { + otherFilesLock.Lock() + defer otherFilesLock.Unlock() otherFiles = append(otherFiles, path) } @@ -71,13 +77,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b list.PrepareIndex() } - transaction, err := collection.db.OpenTransaction() - if err != nil { - return nil, nil, err - } - defer transaction.Discard() - - checksumStorage := checksumStorageProvider(transaction) + checksumStorage := checksumStorageProvider(collection.db) for _, file := range packageFiles { var ( @@ -201,7 +201,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b continue } - err = collection.UpdateInTransaction(p, transaction) + err = collection.Update(p) if err != nil { reporter.Warning("Unable to save package %s: %s", p, err) failedFiles = append(failedFiles, file) @@ -209,7 +209,7 @@ 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) + conflictingPackages := list.Search(Dependency{Pkg: p.Name, Version: p.Version, Relation: VersionEqual, Architecture: p.Architecture}, true, false) for _, cp := range conflictingPackages { reporter.Removed("%s removed due to conflict with package being added", cp) list.Remove(cp) @@ -218,7 +218,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b err = list.Add(p) if err != nil { - reporter.Warning("Unable to add package to repo %s: %s", p, err) + reporter.Warning("Unable to add package: %s", err) failedFiles = append(failedFiles, file) continue } @@ -227,6 +227,6 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b processedFiles = append(processedFiles, candidateProcessedFiles...) } - err = transaction.Commit() + err = nil // reset error as only failed files are reported return } diff --git a/deb/index_files.go b/deb/index_files.go index d8fb7dc5..27a19470 100644 --- a/deb/index_files.go +++ b/deb/index_files.go @@ -143,19 +143,20 @@ func (file *indexFile) Finalize(signer pgp.Signer) error { } if signer != nil { + gpgExt := ".gpg" if file.detachedSign { - err = signer.DetachedSign(file.tempFilename, file.tempFilename+".gpg") + err = signer.DetachedSign(file.tempFilename, file.tempFilename+gpgExt) if err != nil { return fmt.Errorf("unable to detached sign file: %s", err) } if file.parent.suffix != "" { - file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg")] = - filepath.Join(file.parent.basePath, file.relativePath+".gpg") + file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+gpgExt)] = + filepath.Join(file.parent.basePath, file.relativePath+gpgExt) } - err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg"), - file.tempFilename+".gpg") + err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+gpgExt), + file.tempFilename+gpgExt) if err != nil { return fmt.Errorf("unable to publish file: %s", err) } @@ -248,7 +249,7 @@ func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, s } } -func (files *indexFiles) PackageIndex(component, arch string, udeb, installer bool) *indexFile { +func (files *indexFiles) PackageIndex(component, arch string, udeb bool, installer bool, distribution string) *indexFile { if arch == ArchitectureSource { udeb = false } @@ -263,7 +264,11 @@ func (files *indexFiles) PackageIndex(component, arch string, udeb, installer bo if udeb { relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Packages") } else if installer { - relativePath = filepath.Join(component, fmt.Sprintf("installer-%s", arch), "current", "images", "SHA256SUMS") + if distribution == aptly.DistributionFocal { + relativePath = filepath.Join(component, fmt.Sprintf("installer-%s", arch), "current", "legacy-images", "SHA256SUMS") + } else { + relativePath = filepath.Join(component, fmt.Sprintf("installer-%s", arch), "current", "images", "SHA256SUMS") + } } else { relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages") } @@ -384,6 +389,27 @@ func (files *indexFiles) LegacyContentsIndex(arch string, udeb bool) *indexFile return file } +func (files *indexFiles) SkelIndex(component, path string) *indexFile { + key := fmt.Sprintf("si-%s-%s", component, path) + file, ok := files.indexes[key] + + if !ok { + relativePath := filepath.Join(component, path) + + file = &indexFile{ + parent: files, + discardable: false, + compressable: false, + onlyGzip: false, + relativePath: relativePath, + } + + files.indexes[key] = file + } + + return file +} + func (files *indexFiles) ReleaseFile() *indexFile { return &indexFile{ parent: files, diff --git a/deb/list.go b/deb/list.go index ec14bb98..28644e40 100644 --- a/deb/list.go +++ b/deb/list.go @@ -2,6 +2,7 @@ package deb import ( "fmt" + "regexp" "sort" "strings" @@ -138,14 +139,14 @@ func (l *PackageList) Add(p *Package) error { existing, ok := l.packages[key] if ok { if !existing.Equals(p) { - return &PackageConflictError{fmt.Errorf("conflict in package %s", p)} + return &PackageConflictError{fmt.Errorf("package already exists and is different: %s", p)} } return nil } l.packages[key] = p if l.indexed { - for _, provides := range p.Provides { + for _, provides := range p.ProvidedPackages() { l.providesIndex[provides] = append(l.providesIndex[provides], p) } @@ -201,7 +202,7 @@ func (l *PackageList) Append(pl *PackageList) error { existing, ok := l.packages[k] if ok { if !existing.Equals(p) { - return fmt.Errorf("conflict in package %s", p) + return fmt.Errorf("package already exists and is different: %s", p) } } else { l.packages[k] = p @@ -215,7 +216,7 @@ func (l *PackageList) Append(pl *PackageList) error { func (l *PackageList) Remove(p *Package) { delete(l.packages, l.keyFunc(p)) if l.indexed { - for _, provides := range p.Provides { + for _, provides := range p.ProvidedPackages() { for i, pkg := range l.providesIndex[provides] { if pkg.Equals(p) { // remove l.ProvidesIndex[provides][i] w/o preserving order @@ -317,6 +318,9 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so progress.InitBar(int64(l.Len())*int64(len(architectures)), false, aptly.BarGeneralVerifyDependencies) } + if len(architectures) == 0 { + return nil, fmt.Errorf("no architectures defined, cannot verify dependencies") + } for _, arch := range architectures { cache := make(map[string]bool, 2048) @@ -347,11 +351,11 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so hash := dep.Hash() satisfied, ok := cache[hash] if !ok { - satisfied = sources.Search(dep, false) != nil + satisfied = sources.Search(dep, false, true) != nil cache[hash] = satisfied } - if !satisfied && !ok { + if !satisfied { variantsMissing = append(variantsMissing, dep) } @@ -366,6 +370,8 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so } } + missing = depSliceDeduplicate(missing) + if progress != nil { progress.ShutdownBar() } @@ -417,7 +423,7 @@ func (l *PackageList) PrepareIndex() { l.packagesIndex[i] = p i++ - for _, provides := range p.Provides { + for _, provides := range p.ProvidedPackages() { l.providesIndex[provides] = append(l.providesIndex[provides], p) } } @@ -457,7 +463,7 @@ func (l *PackageList) SearchByKey(arch, name, version string) (result *PackageLi } // Search searches package index for specified package(s) using optimized queries -func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*Package) { +func (l *PackageList) Search(dep Dependency, allMatches bool, searchProvided bool) (searchResults []*Package) { if !l.indexed { panic("list not indexed, can't search") } @@ -470,20 +476,26 @@ func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []* searchResults = append(searchResults, p) if !allMatches { - break + return } } i++ } - if dep.Relation == VersionDontCare { - for _, p := range l.providesIndex[dep.Pkg] { + if searchProvided { + providers, ok := l.providesIndex[dep.Pkg] + if !ok { + return + } + for _, p := range providers { if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) { - searchResults = append(searchResults, p) + if p.MatchesDependency(dep) { + searchResults = append(searchResults, p) + } if !allMatches { - break + return } } } @@ -492,32 +504,80 @@ func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []* return } -// Filter filters package index by specified queries (ORed together), possibly pulling dependencies -func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) { - return l.FilterWithProgress(queries, withDependencies, source, dependencyOptions, architecturesList, nil) +// FilterOptions specifies options for Filter() +type FilterOptions struct { + Queries []PackageQuery + WithDependencies bool + WithSources bool // Source packages corresponding to binary packages are included + Source *PackageList + DependencyOptions int + Architectures []string + Progress aptly.Progress // set to non-nil value to report progress } -// FilterWithProgress filters package index by specified queries (ORed together), possibly pulling dependencies and displays progress -func (l *PackageList) FilterWithProgress(queries []PackageQuery, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string, progress aptly.Progress) (*PackageList, error) { +// SourceRegex is a regular expression to match source package names. +// > In a binary package control file [...], the source package name may be followed by a version number in +// > parentheses. This version number may be omitted [...] if it has the same value as the Version field of +// > the binary package in question. +// > [...] +// > Package names (both source and binary, see Package) must consist only of lower case letters (a-z), +// > digits (0-9), plus (+) and minus (-) signs, and periods (.). +// > They must be at least two characters long and must start with an alphanumeric character. +// -- https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-source +var SourceRegex = regexp.MustCompile(`^([a-z0-9][-+.a-z0-9]+)(?:\s+\(([^)]+)\))?$`) + +// Filter filters package index by specified queries (ORed together), possibly pulling dependencies +func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) { if !l.indexed { panic("list not indexed, can't filter") } result := NewPackageList() - for _, query := range queries { - result.Append(query.Query(l)) + for _, query := range options.Queries { + _ = result.Append(query.Query(l)) + } + // The above loop already finds source packages that are named equal to their binary package, but we still need + // to account for those that are named differently. + if options.WithSources { + sourceQueries := make([]PackageQuery, 0) + for _, pkg := range result.packages { + if pkg.Source == "" { + continue + } + matches := SourceRegex.FindStringSubmatch(pkg.Source) + if matches == nil { + return nil, fmt.Errorf("invalid Source field: %s", pkg.Source) + } + sourceName := matches[1] + if sourceName == pkg.Name { + continue + } + sourceVersion := pkg.Version + if matches[2] != "" { + sourceVersion = matches[2] + } + sourceQueries = append(sourceQueries, &DependencyQuery{Dependency{ + Pkg: sourceName, + Version: sourceVersion, + Relation: VersionEqual, + Architecture: ArchitectureSource, + }}) + } + for _, query := range sourceQueries { + _ = result.Append(query.Query(l)) + } } - if withDependencies { + if options.WithDependencies { added := result.Len() result.PrepareIndex() dependencySource := NewPackageList() - if source != nil { - dependencySource.Append(source) + if options.Source != nil { + _ = dependencySource.Append(options.Source) } - dependencySource.Append(result) + _ = dependencySource.Append(result) dependencySource.PrepareIndex() // while some new dependencies were discovered @@ -525,47 +585,47 @@ func (l *PackageList) FilterWithProgress(queries []PackageQuery, withDependencie added = 0 // find missing dependencies - missing, err := result.VerifyDependencies(dependencyOptions, architecturesList, dependencySource, progress) + missing, err := result.VerifyDependencies(options.DependencyOptions, options.Architectures, dependencySource, options.Progress) if err != nil { return nil, err } // try to satisfy dependencies for _, dep := range missing { - if dependencyOptions&DepFollowAllVariants == 0 { + if options.DependencyOptions&DepFollowAllVariants == 0 { // dependency might have already been satisfied // with packages already been added // // when follow-all-variants is enabled, we need to try to expand anyway, // as even if dependency is satisfied now, there might be other ways to satisfy dependency - if result.Search(dep, false) != nil { - if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil { - progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true)) + if result.Search(dep, false, true) != nil { + if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil { + options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true)) } continue } } - searchResults := l.Search(dep, true) + searchResults := l.Search(dep, true, true) if len(searchResults) > 0 { for _, p := range searchResults { if result.Has(p) { continue } - if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil { - progress.ColoredPrintf("@{g}Injecting package@|: %s", p) + if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil { + options.Progress.ColoredPrintf("@{g}Injecting package@|: %s", p) } - result.Add(p) - dependencySource.Add(p) + _ = result.Add(p) + _ = dependencySource.Add(p) added++ - if dependencyOptions&DepFollowAllVariants == 0 { + if options.DependencyOptions&DepFollowAllVariants == 0 { break } } } else { - if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil { - progress.ColoredPrintf("@{r}Unsatisfied dependency@|: %s", dep.String()) + if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil { + options.Progress.ColoredPrintf("@{r}Unsatisfied dependency@|: %s", dep.String()) } } diff --git a/deb/list_test.go b/deb/list_test.go index 91769179..efff77a3 100644 --- a/deb/list_test.go +++ b/deb/list_test.go @@ -131,7 +131,7 @@ func (s *PackageListSuite) TestAddLen(c *C) { c.Check(s.list.Len(), Equals, 1) c.Check(s.list.Add(s.p3), IsNil) c.Check(s.list.Len(), Equals, 2) - c.Check(s.list.Add(s.p4), ErrorMatches, "conflict in package.*") + c.Check(s.list.Add(s.p4), ErrorMatches, "package already exists and is different: .*") } func (s *PackageListSuite) TestRemove(c *C) { @@ -243,7 +243,7 @@ func (s *PackageListSuite) TestAppend(c *C) { list.Add(s.p4) err = s.list.Append(list) - c.Check(err, ErrorMatches, "conflict.*") + c.Check(err, ErrorMatches, "package already exists and is different: .*") s.list.PrepareIndex() c.Check(func() { s.list.Append(s.il) }, Panics, "Append not supported when indexed") @@ -251,59 +251,71 @@ func (s *PackageListSuite) TestAppend(c *C) { func (s *PackageListSuite) TestSearch(c *C) { //allMatches = False - c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}, false) }, Panics, "list not indexed, can't search") + c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}, false, true) }, Panics, "list not indexed, can't search") - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}, false), DeepEquals, []*Package{s.packages[4]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}, false, true), DeepEquals, []*Package{s.packages[4]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, false, true), IsNil) // search w/o version should return package with latest version - c.Check(s.il.Search(Dependency{Architecture: "source", Pkg: "dpkg"}, false), DeepEquals, []*Package{s.packages[13]}) + c.Check(s.il.Search(Dependency{Architecture: "source", Pkg: "dpkg"}, false, true), DeepEquals, []*Package{s.packages[13]}) // allMatches = True - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "mail-agent"}, true), Contains, []*Package{s.packages2[0], s.packages2[1]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "puppy"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "mail-agent"}, true, true), Contains, []*Package{s.packages2[0], s.packages2[1]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "puppy"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "3"}, true), Contains, []*Package{s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "3"}, true, true), Contains, []*Package{s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.1~"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.2"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.1~"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1-bp1"}, true), Contains, []*Package{s.packages2[2]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.0"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.2"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1-bp1"}, true, true), Contains, []*Package{s.packages2[2]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.0"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "5.0"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "1.1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "5.0"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true, true), Contains, []*Package{s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true, true), IsNil) + + // Provides with version number + python3CFFIBackend := &Package{Name: "python3-cffi-backend", Version: "1.15.1-5+b1", Architecture: "amd64", Provides: []string{"python3-cffi-backend-api-9729", "python3-cffi-backend-api-max (= 10495)", "python3-cffi-backend-api-min (= 9729)"}} + err := s.il2.Add(python3CFFIBackend) + c.Check(err, IsNil) + c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionGreaterOrEqual, Version: "9729"}, false, true), DeepEquals, []*Package{python3CFFIBackend}) + c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionLess, Version: "9729"}, false, true), IsNil) + c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionDontCare}, false, true), DeepEquals, []*Package{python3CFFIBackend}) } func (s *PackageListSuite) TestFilter(c *C) { - c.Check(func() { s.list.Filter([]PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}}, false, nil, 0, nil) }, Panics, "list not indexed, can't filter") + c.Check(func() { + s.list.Filter(FilterOptions{ + Queries: []PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}}, + }) + }, Panics, "list not indexed, can't filter") plString := func(l *PackageList) string { list := make([]string, 0, l.Len()) @@ -316,83 +328,136 @@ func (s *PackageListSuite) TestFilter(c *C) { return strings.Join(list, " ") } - result, err := s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}}, false, nil, 0, nil) + result, err := s.il.Filter(FilterOptions{Queries: []PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}}}) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.1~bp1_i386") - result, err = s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, &PkgQuery{"dpkg", "1.7", "source"}, - &PkgQuery{"dpkg", "1.8", "amd64"}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{Queries: []PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, &PkgQuery{"dpkg", "1.7", "source"}, + &PkgQuery{"dpkg", "1.8", "amd64"}}}) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_source") - result, err = s.il.Filter([]PackageQuery{ + result, err = s.il.Filter(FilterOptions{Queries: []PackageQuery{ &DependencyQuery{Dep: Dependency{Pkg: "app"}}, &DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}, &DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}}, &DependencyQuery{Dep: Dependency{Pkg: "xyz"}}, - &DependencyQuery{Dep: Dependency{Pkg: "aa", Relation: VersionGreater, Version: "3.0"}}}, false, nil, 0, nil) + &DependencyQuery{Dep: Dependency{Pkg: "aa", Relation: VersionGreater, Version: "3.0"}}}}) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source") - result, err = s.il.Filter([]PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "app", Architecture: "i386"}}}, true, NewPackageList(), 0, []string{"i386"}) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "app", Architecture: "i386"}}}, + WithDependencies: true, + Source: NewPackageList(), + Architectures: []string{"i386"}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386") - result, err = s.il.Filter([]PackageQuery{ - &DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "0.9"}}, - &DependencyQuery{Dep: Dependency{Pkg: "lib"}}, - &DependencyQuery{Dep: Dependency{Pkg: "data"}}}, true, NewPackageList(), 0, []string{"i386", "amd64"}) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{ + &DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "0.9"}}, + &DependencyQuery{Dep: Dependency{Pkg: "lib"}}, + &DependencyQuery{Dep: Dependency{Pkg: "data"}}}, + WithDependencies: true, + Source: NewPackageList(), + Architectures: []string{"i386", "amd64"}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386") - result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, - &DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, + &DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source") - result, err = s.il.Filter([]PackageQuery{&AndQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, - &DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&AndQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, + &DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "") - result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, - &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, + &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_i386 data_1.1~bp1_all") - result, err = s.il.Filter([]PackageQuery{&AndQuery{&FieldQuery{Field: "Version", Relation: VersionGreaterOrEqual, Value: "1.0"}, - &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&AndQuery{&FieldQuery{Field: "Version", Relation: VersionGreaterOrEqual, Value: "1.0"}, + &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.0_s390 data_1.1~bp1_all") - result, err = s.il.Filter([]PackageQuery{&AndQuery{ - &FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&AndQuery{ + &FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.1~bp1_i386") - result, err = s.il.Filter([]PackageQuery{&NotQuery{ - &FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&NotQuery{ + &FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.6.1-3_arm dpkg_1.6.1-3_source dpkg_1.7_source libx_1.5_arm") - result, err = s.il.Filter([]PackageQuery{&AndQuery{ - &FieldQuery{Field: "$Architecture", Relation: VersionRegexp, Value: "i.*6", Regexp: regexp.MustCompile("i.*6")}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&AndQuery{ + &FieldQuery{Field: "$Architecture", Relation: VersionRegexp, Value: "i.*6", Regexp: regexp.MustCompile("i.*6")}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "app_1.1~bp1_i386") - result, err = s.il.Filter([]PackageQuery{&AndQuery{ - &FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")}, - &NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}}, - }}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&AndQuery{ + &FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")}, + &NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}}, + }}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "aa_2.0-1_i386 app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 mailer_3.5.8_i386") - result, err = s.il.Filter([]PackageQuery{&AndQuery{ - &NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}}, - &FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")}, - }}, false, nil, 0, nil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&AndQuery{ + &NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}}, + &FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")}, + }}, + }) c.Check(err, IsNil) c.Check(plString(result), Equals, "aa_2.0-1_i386 app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 mailer_3.5.8_i386") + + // Different version for the source package + for _, p := range s.sourcePackages { + c.Check(s.il.Add(p), IsNil) + } + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "lib"}}}, + Architectures: []string{"i386", "amd64"}, + WithSources: true, + }) + c.Check(err, IsNil) + c.Check(plString(result), Equals, "lib_0.9_source lib_1.0_i386") + + // Different name for the source package + err = s.il.Add(&Package{Name: "glibc", Version: "1.0", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}) + c.Check(err, IsNil) + err = s.il.Add(&Package{Name: "libc1", Version: "1.0", Architecture: "i386", Source: "glibc", deps: &PackageDependencies{}}) + c.Check(err, IsNil) + result, err = s.il.Filter(FilterOptions{ + Queries: []PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "libc1"}}}, + Architectures: []string{"i386"}, + WithSources: true, + }) + c.Check(err, IsNil) + c.Check(plString(result), Equals, "glibc_1.0_source libc1_1.0_i386") } func (s *PackageListSuite) TestVerifyDependencies(c *C) { diff --git a/deb/local.go b/deb/local.go index 2a15c734..1b09fdbd 100644 --- a/deb/local.go +++ b/deb/local.go @@ -116,7 +116,7 @@ func (collection *LocalRepoCollection) search(filter func(*LocalRepo) bool, uniq return result } - collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error { + collection.db.ProcessByPrefix([]byte("L"), func(_, blob []byte) error { r := &LocalRepo{} if err := r.Decode(blob); err != nil { log.Printf("Error decoding local repo: %s\n", err) @@ -219,7 +219,7 @@ func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) { // ForEach runs method for each repository func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) error { - return collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error { + return collection.db.ProcessByPrefix([]byte("L"), func(_, blob []byte) error { r := &LocalRepo{} if err := r.Decode(blob); err != nil { log.Printf("Error decoding repo: %s\n", err) diff --git a/deb/package.go b/deb/package.go index ce16adf2..fc4c9b68 100644 --- a/deb/package.go +++ b/deb/package.go @@ -10,6 +10,7 @@ import ( "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/utils" + "github.com/rs/zerolog/log" ) // Package is single instance of Debian package @@ -189,7 +190,12 @@ func NewInstallerPackageFromControlFile(input Stanza, repo *RemoteRepo, componen return nil, err } - relPath := filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "images") + var relPath string + if repo.Distribution == aptly.DistributionFocal { + relPath = filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "legacy-images") + } else { + relPath = filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "images") + } for i := range files { files[i].downloadPath = relPath @@ -307,6 +313,23 @@ func (p *Package) GetField(name string) string { } } +// ProvidedPackages returns just the package names of the provided packages (without version numbers such as +// `(= 1.2.3)`). +func (p *Package) ProvidedPackages() []string { + result := make([]string, len(p.Provides)) + for i, provided := range p.Provides { + providedDep, err := ParseDependency(provided) + if err != nil { + // Should never happen, but I included this, so it definitely has the old behavior in case there is no + // special syntax. + result[i] = provided + } else { + result[i] = providedDep.Pkg + } + } + return result +} + // MatchesArchitecture checks whether packages matches specified architecture func (p *Package) MatchesArchitecture(arch string) bool { if p.Architecture == ArchitectureAll && arch != ArchitectureSource { @@ -316,24 +339,73 @@ func (p *Package) MatchesArchitecture(arch string) bool { return p.Architecture == arch } +func JoinErrors(errs ...error) error { + var combinedErr error + for _, err := range errs { + if err != nil { + if combinedErr == nil { + combinedErr = err + } else { + combinedErr = fmt.Errorf("%w\n%v", combinedErr, err) + } + } + } + return combinedErr +} + +// providesDependency checks if the package `Provide:`s the dependency, assuming that the architecture matches. +// If the `Provides:` entry includes a version number, it will be considered when checking the dependency. +func (p *Package) providesDependency(dep Dependency) (bool, error) { + var errs []error // won't cause an allocation in case of no errors + for _, provided := range p.Provides { + providedDep, err := ParseDependency(provided) + if err != nil { + errs = append(errs, err) + } + if providedDep.Relation != VersionEqual && providedDep.Relation != VersionDontCare { + // The only relation allowed here is `=`. + // > The relations allowed are [...]. The exception is the Provides field, for which only = is allowed. + // > [...] + // > A Provides field may contain version numbers, and such a version number will be considered when + // > considering a dependency on or conflict with the virtual package name. + // -- https://www.debian.org/doc/debian-policy/ch-relationships.html + errs = append(errs, fmt.Errorf("unsupported relation in Provides: %s", providedDep.String())) + continue + } + providedVersion := providedDep.Version + if providedVersion == "" { + providedVersion = p.Version + } + if providedDep.Pkg == dep.Pkg && versionSatisfiesDependency(providedVersion, dep) { + return true, nil + } + } + return false, JoinErrors(errs...) +} + // MatchesDependency checks whether package matches specified dependency func (p *Package) MatchesDependency(dep Dependency) bool { if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) { return false } - if dep.Relation == VersionDontCare { - if utils.StrSliceHasItem(p.Provides, dep.Pkg) { - return true - } - return dep.Pkg == p.Name + providesDep, err := p.providesDependency(dep) + if err != nil { + log.Warn().Err(err).Msg("Error while checking if package provides dependency") + } + if providesDep { + return true } if dep.Pkg != p.Name { return false } - r := CompareVersions(p.Version, dep.Version) + return versionSatisfiesDependency(p.Version, dep) +} + +func versionSatisfiesDependency(version string, dep Dependency) bool { + r := CompareVersions(version, dep.Version) switch dep.Relation { case VersionEqual: @@ -347,13 +419,15 @@ func (p *Package) MatchesDependency(dep Dependency) bool { case VersionGreaterOrEqual: return r >= 0 case VersionPatternMatch: - matched, err := filepath.Match(dep.Version, p.Version) + matched, err := filepath.Match(dep.Version, version) return err == nil && matched case VersionRegexp: - return dep.Regexp.FindStringIndex(p.Version) != nil + return dep.Regexp.FindStringIndex(version) != nil + case VersionDontCare: + return true + default: + panic(fmt.Sprintf("unknown relation: %d", dep.Relation)) } - - panic("unknown relation") } // GetName returns package name @@ -376,7 +450,7 @@ func (p *Package) GetArchitecture() string { return p.Architecture } -// GetDependencies compiles list of dependncies by flags from options +// GetDependencies compiles list of dependencies by flags from options func (p *Package) GetDependencies(options int) (dependencies []string) { deps := p.Deps() @@ -621,9 +695,7 @@ func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packageP return err } - publishedDirectory := filepath.Join(prefix, relPath) - - err = publishedStorage.LinkFromPool(publishedDirectory, f.Filename, packagePool, sourcePoolPath, f.Checksums, force) + err = publishedStorage.LinkFromPool(prefix, relPath, f.Filename, packagePool, sourcePoolPath, f.Checksums, force) if err != nil { return err } diff --git a/deb/package_collection.go b/deb/package_collection.go index 2da7fe67..0027f1de 100644 --- a/deb/package_collection.go +++ b/deb/package_collection.go @@ -317,7 +317,7 @@ func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList) } // Search is not implemented -func (collection *PackageCollection) Search(dep Dependency, allMatches bool) (searchResults []*Package) { +func (collection *PackageCollection) Search(_ Dependency, _ bool, _ bool) (searchResults []*Package) { panic("Not implemented") } diff --git a/deb/package_test.go b/deb/package_test.go index b3817d14..18f8040d 100644 --- a/deb/package_test.go +++ b/deb/package_test.go @@ -333,9 +333,12 @@ func (s *PackageSuite) TestMatchesDependency(c *C) { // Provides c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, false) - p.Provides = []string{"fun", "game"} + p.Provides = []string{"fun (= 42)", "game"} c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, true) c.Check(p.MatchesDependency(Dependency{Pkg: "game", Architecture: "amd64", Relation: VersionDontCare}), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "fun", Relation: VersionDontCare}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "fun", Relation: VersionGreaterOrEqual, Version: "42"}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "fun", Relation: VersionLess, Version: "42"}), Equals, false) } func (s *PackageSuite) TestGetDependencies(c *C) { diff --git a/deb/publish.go b/deb/publish.go index 7a7bab88..3bf397af 100644 --- a/deb/publish.go +++ b/deb/publish.go @@ -5,7 +5,6 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -22,6 +21,16 @@ import ( "github.com/aptly-dev/aptly/utils" ) +type SourceEntry struct { + Component, Name string +} + +type PublishedRepoUpdateResult struct { + AddedSources map[string]string + UpdatedSources map[string]string + RemovedSources map[string]string +} + type repoSourceItem struct { // Pointer to snapshot if SourceKind == "snapshot" snapshot *Snapshot @@ -44,6 +53,7 @@ type PublishedRepo struct { ButAutomaticUpgrades string Label string Suite string + Codename string // Architectures is a list of all architectures published Architectures []string // SourceKind is "local"/"repo" @@ -70,6 +80,195 @@ type PublishedRepo struct { // Provide index files per hash also AcquireByHash bool + + // Support multiple distributions + MultiDist bool + + // Revision + Revision *PublishedRepoRevision +} + +type PublishedRepoRevision struct { + // Map of sources: component name -> snapshot name/local repo Name + Sources map[string]string +} + +func (result *PublishedRepoUpdateResult) AddedComponents() []string { + components := make([]string, 0, len(result.AddedSources)) + for component := range result.AddedSources { + components = append(components, component) + } + sort.Strings(components) + + return components +} + +func (result *PublishedRepoUpdateResult) UpdatedComponents() []string { + components := make([]string, 0, len(result.UpdatedSources)) + for component := range result.UpdatedSources { + components = append(components, component) + } + sort.Strings(components) + + return components +} + +func (result *PublishedRepoUpdateResult) RemovedComponents() []string { + components := make([]string, 0, len(result.RemovedSources)) + for component := range result.RemovedSources { + components = append(components, component) + } + sort.Strings(components) + + return components +} + +func (revision *PublishedRepoRevision) Components() []string { + components := make([]string, 0, len(revision.Sources)) + for component := range revision.Sources { + components = append(components, component) + } + sort.Strings(components) + + return components +} + +func (revision *PublishedRepoRevision) SourceList() []SourceEntry { + sources := revision.Sources + components := revision.Components() + sourceList := make([]SourceEntry, 0, len(sources)) + for _, component := range components { + name := sources[component] + sourceList = append(sourceList, SourceEntry{ + Component: component, + Name: name, + }) + } + + return sourceList +} + +func (revision *PublishedRepoRevision) SourceNames() []string { + sources := revision.Sources + names := make([]string, 0, len(sources)) + for _, component := range revision.Components() { + names = append(names, sources[component]) + } + + return names +} + +func (p *PublishedRepo) DropRevision() *PublishedRepoRevision { + revision := p.Revision + p.Revision = nil + + return revision +} + +func (p *PublishedRepo) ObtainRevision() *PublishedRepoRevision { + revision := p.Revision + if revision == nil { + sources := make(map[string]string, len(p.Sources)) + for _, component := range p.Components() { + item := p.sourceItems[component] + if item.snapshot != nil { + sources[component] = item.snapshot.Name + } else if item.localRepo != nil { + sources[component] = item.localRepo.Name + } else { + panic("no snapshot/localRepo") + } + } + revision = &PublishedRepoRevision{ + Sources: sources, + } + p.Revision = revision + } + return revision +} + +func (p *PublishedRepo) Update(collectionFactory *CollectionFactory, _ aptly.Progress) (*PublishedRepoUpdateResult, error) { + result := &PublishedRepoUpdateResult{ + AddedSources: map[string]string{}, + UpdatedSources: map[string]string{}, + RemovedSources: map[string]string{}, + } + + revision := p.DropRevision() + if revision == nil { + if p.SourceKind == SourceLocalRepo { + // Re-fetch packages from local repository + for component, item := range p.sourceItems { + localRepo := item.localRepo + if localRepo != nil { + p.UpdateLocalRepo(component, localRepo) + result.UpdatedSources[component] = localRepo.Name + } + } + } + } else { + for _, component := range p.Components() { + name, exists := revision.Sources[component] + if !exists { + p.RemoveComponent(component) + result.RemovedSources[component] = name + } + } + + if p.SourceKind == SourceLocalRepo { + localRepoCollection := collectionFactory.LocalRepoCollection() + for component, name := range revision.Sources { + localRepo, err := localRepoCollection.ByName(name) + if err != nil { + return result, fmt.Errorf("unable to update: %s", err) + } + + err = localRepoCollection.LoadComplete(localRepo) + if err != nil { + return result, fmt.Errorf("unable to update: %s", err) + } + + _, exists := p.Sources[component] + if exists { + // Even in the case, when the local repository has not been changed as package source, + // it may contain a modified set of packages that requires (re-)publication. + p.UpdateLocalRepo(component, localRepo) + result.UpdatedSources[component] = name + } else { + p.UpdateLocalRepo(component, localRepo) + result.AddedSources[component] = name + } + } + } else if p.SourceKind == SourceSnapshot { + snapshotCollection := collectionFactory.SnapshotCollection() + for component, name := range revision.Sources { + snapshot, err := snapshotCollection.ByName(name) + if err != nil { + return result, fmt.Errorf("unable to update: %s", err) + } + + err = snapshotCollection.LoadComplete(snapshot) + if err != nil { + return result, fmt.Errorf("unable to update: %s", err) + } + + sourceUUID, exists := p.Sources[component] + if exists { + if snapshot.UUID != sourceUUID { + p.UpdateSnapshot(component, snapshot) + result.UpdatedSources[component] = name + } + } else { + p.UpdateSnapshot(component, snapshot) + result.AddedSources[component] = name + } + } + } else { + return result, fmt.Errorf("unknown published repository type") + } + } + + return result, nil } // ParsePrefix splits [storage:]prefix into components @@ -153,13 +352,14 @@ func walkUpTree(source interface{}, collectionFactory *CollectionFactory) (rootD // distribution and architectures are user-defined properties // components & sources are lists of component to source mapping (*Snapshot or *LocalRepo) func NewPublishedRepo(storage, prefix, distribution string, architectures []string, - components []string, sources []interface{}, collectionFactory *CollectionFactory) (*PublishedRepo, error) { + components []string, sources []interface{}, collectionFactory *CollectionFactory, multiDist bool) (*PublishedRepo, error) { result := &PublishedRepo{ UUID: uuid.New(), Storage: storage, Architectures: architectures, Sources: make(map[string]string), sourceItems: make(map[string]repoSourceItem), + MultiDist: multiDist, } if len(sources) == 0 { @@ -197,9 +397,6 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri if distribution == "" || component == "" { rootDistributions, rootComponents := walkUpTree(source, collectionFactory) if distribution == "" { - for i := range rootDistributions { - rootDistributions[i] = strings.Replace(rootDistributions[i], "/", "-", -1) - } discoveredDistributions = append(discoveredDistributions, rootDistributions...) } if component == "" { @@ -264,10 +461,6 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri } } - if strings.Contains(distribution, "/") { - return nil, fmt.Errorf("invalid distribution %s, '/' is not allowed", distribution) - } - result.Distribution = distribution // only fields which are unique by all given snapshots are set on published @@ -284,14 +477,28 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri return result, nil } -// MarshalJSON requires object to be "loaded completely" -func (p *PublishedRepo) MarshalJSON() ([]byte, error) { - type sourceInfo struct { - Component, Name string +func (revision *PublishedRepoRevision) MarshalJSON() ([]byte, error) { + sources := revision.Sources + components := revision.Components() + sourceList := make([]SourceEntry, 0, len(sources)) + for _, component := range components { + name := sources[component] + sourceList = append(sourceList, SourceEntry{ + Component: component, + Name: name, + }) } - sources := []sourceInfo{} - for component, item := range p.sourceItems { + return json.Marshal(map[string]interface{}{ + "Sources": sourceList, + }) +} + +// MarshalJSON requires object to filled by "LoadShallow" or "LoadComplete" +func (p *PublishedRepo) MarshalJSON() ([]byte, error) { + sources := []SourceEntry{} + for _, component := range p.Components() { + item := p.sourceItems[component] name := "" if item.snapshot != nil { name = item.snapshot.Name @@ -300,7 +507,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) { } else { panic("no snapshot/local repo") } - sources = append(sources, sourceInfo{ + sources = append(sources, SourceEntry{ Component: component, Name: name, }) @@ -312,6 +519,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) { "Label": p.Label, "Origin": p.Origin, "Suite": p.Suite, + "Codename": p.Codename, "NotAutomatic": p.NotAutomatic, "ButAutomaticUpgrades": p.ButAutomaticUpgrades, "Prefix": p.Prefix, @@ -321,6 +529,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) { "Storage": p.Storage, "SkipContents": p.SkipContents, "AcquireByHash": p.AcquireByHash, + "MultiDist": p.MultiDist, }) } @@ -366,6 +575,10 @@ func (p *PublishedRepo) String() string { extras = append(extras, fmt.Sprintf("suite: %s", p.Suite)) } + if p.Codename != "" { + extras = append(extras, fmt.Sprintf("codename: %s", p.Codename)) + } + extra = strings.Join(extras, ", ") if extra != "" { @@ -418,26 +631,57 @@ func (p *PublishedRepo) Components() []string { return result } -// UpdateLocalRepo updates content from local repo in component -func (p *PublishedRepo) UpdateLocalRepo(component string) { +// Components returns sorted list of published repo source names +func (p *PublishedRepo) SourceNames() []string { + var sources = []string{} + + for _, component := range p.Components() { + var source string + + item := p.sourceItems[component] + if item.snapshot != nil { + source = item.snapshot.Name + } else if item.localRepo != nil { + source = item.localRepo.Name + } else { + panic("no snapshot/localRepo") + } + + sources = append(sources, fmt.Sprintf("%s:%s", source, component)) + } + + sort.Strings(sources) + return sources +} + +// UpdateLocalRepo inserts/updates local repository source for component +func (p *PublishedRepo) UpdateLocalRepo(component string, localRepo *LocalRepo) { if p.SourceKind != SourceLocalRepo { panic("not local repo publish") } - item := p.sourceItems[component] - item.packageRefs = item.localRepo.RefList() + item, exists := p.sourceItems[component] + if !exists { + item = repoSourceItem{} + } + item.localRepo = localRepo + item.packageRefs = localRepo.RefList() p.sourceItems[component] = item + p.Sources[component] = localRepo.UUID p.rePublishing = true } -// UpdateSnapshot switches snapshot for component +// UpdateSnapshot inserts/updates snapshot source for component func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) { if p.SourceKind != SourceSnapshot { panic("not snapshot publish") } - item := p.sourceItems[component] + item, exists := p.sourceItems[component] + if !exists { + item = repoSourceItem{} + } item.snapshot = snapshot p.sourceItems[component] = item @@ -445,6 +689,14 @@ func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) { p.rePublishing = true } +// RemoveComponent removes component from published repository +func (p *PublishedRepo) RemoveComponent(component string) { + delete(p.Sources, component) + delete(p.sourceItems, component) + + p.rePublishing = true +} + // Encode does msgpack encoding of PublishedRepo func (p *PublishedRepo) Encode() []byte { var buf bytes.Buffer @@ -513,9 +765,55 @@ func (p *PublishedRepo) GetSuite() string { return p.Suite } +// GetCodename returns default or manual Codename: +func (p *PublishedRepo) GetCodename() string { + if p.Codename == "" { + return p.Distribution + } + return p.Codename +} + +// GetSkelFiles returns a map of files to be added to a repo. Key being the relative +// path from component folder, and value being the full local FS path. +func (p *PublishedRepo) GetSkelFiles(skelDir string, component string) (map[string]string, error) { + files := make(map[string]string) + + if skelDir == "" { + return files, nil + } + + fsPath := filepath.Join(skelDir, p.Prefix, "dists", p.Distribution, component) + if err := filepath.Walk(fsPath, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return err + } + + stat, err := os.Stat(path) + if err != nil { + return err + } + + if !stat.Mode().IsRegular() { + return nil + } + + relativePath, err := filepath.Rel(fsPath, path) + if err != nil { + return err + } + + files[relativePath] = path + return nil + }); err != nil && !os.IsNotExist(err) { + return files, err + } + + return files, nil +} + // Publish publishes snapshot (repository) contents, links package files, generates Packages & Release files, signs them func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageProvider aptly.PublishedStorageProvider, - collectionFactory *CollectionFactory, signer pgp.Signer, progress aptly.Progress, forceOverwrite bool) error { + collectionFactory *CollectionFactory, signer pgp.Signer, progress aptly.Progress, forceOverwrite bool, skelDir string) error { publishedStorage := publishedStorageProvider.GetPublishedStorage(p.Storage) err := publishedStorage.MkDir(filepath.Join(p.Prefix, "pool")) @@ -582,7 +880,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP } var tempDir string - tempDir, err = ioutil.TempDir(os.TempDir(), "aptly") + tempDir, err = os.MkdirTemp(os.TempDir(), "aptly") if err != nil { return err } @@ -605,7 +903,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP // For all architectures, pregenerate packages/sources files for _, arch := range p.Architectures { - indexes.PackageIndex(component, arch, false, false) + indexes.PackageIndex(component, arch, false, false, p.Distribution) } list.PrepareIndex() @@ -627,9 +925,18 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP if err2 != nil { return err2 } - relPath = filepath.Join("pool", component, poolDir) + if p.MultiDist { + relPath = filepath.Join("pool", p.Distribution, component, poolDir) + } else { + relPath = filepath.Join("pool", component, poolDir) + } + } else { - relPath = filepath.Join("dists", p.Distribution, component, fmt.Sprintf("%s-%s", pkg.Name, arch), "current", "images") + if p.Distribution == aptly.DistributionFocal { + relPath = filepath.Join("dists", p.Distribution, component, fmt.Sprintf("%s-%s", pkg.Name, arch), "current", "legacy-images") + } else { + relPath = filepath.Join("dists", p.Distribution, component, fmt.Sprintf("%s-%s", pkg.Name, arch), "current", "images") + } } err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, relPath, forceOverwrite) @@ -667,7 +974,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP } } - bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb, pkg.IsInstaller).BufWriter() + bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb, pkg.IsInstaller, p.Distribution).BufWriter() if err != nil { return err } @@ -715,13 +1022,37 @@ 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) + } + } + } + udebs := []bool{false} if hadUdebs { udebs = append(udebs, true) // For all architectures, pregenerate .udeb indexes for _, arch := range p.Architectures { - indexes.PackageIndex(component, arch, true, false) + indexes.PackageIndex(component, arch, true, false, p.Distribution) } } @@ -735,6 +1066,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP release["Origin"] = p.GetOrigin() release["Label"] = p.GetLabel() release["Suite"] = p.GetSuite() + release["Codename"] = p.GetCodename() if p.AcquireByHash { release["Acquire-By-Hash"] = "yes" } @@ -793,7 +1125,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP } release["Label"] = p.GetLabel() release["Suite"] = p.GetSuite() - release["Codename"] = p.Distribution + release["Codename"] = p.GetCodename() release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST") release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ") if p.AcquireByHash { @@ -953,8 +1285,11 @@ func (collection *PublishedRepoCollection) Update(repo *PublishedRepo) error { return batch.Write() } -// LoadComplete loads additional information for remote repo -func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) { +// LoadShallow loads basic information on the repo's sources +// +// This does not *fully* load in the sources themselves and their packages. +// It's useful if you just want to use JSON serialization without loading in unnecessary things. +func (collection *PublishedRepoCollection) LoadShallow(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) { repo.sourceItems = make(map[string]repoSourceItem) if repo.SourceKind == SourceSnapshot { @@ -965,10 +1300,6 @@ func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, col if err != nil { return } - err = collectionFactory.SnapshotCollection().LoadComplete(item.snapshot) - if err != nil { - return - } repo.sourceItems[component] = item } @@ -980,6 +1311,30 @@ func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, col if err != nil { return } + + item.packageRefs = &PackageRefList{} + repo.sourceItems[component] = item + } + } else { + panic("unknown SourceKind") + } + + return +} + +// LoadComplete loads complete information on the sources of the repo *and* their packages +func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) { + collection.LoadShallow(repo, collectionFactory) + + if repo.SourceKind == SourceSnapshot { + for _, item := range repo.sourceItems { + err = collectionFactory.SnapshotCollection().LoadComplete(item.snapshot) + if err != nil { + return + } + } + } else if repo.SourceKind == SourceLocalRepo { + for component, item := range repo.sourceItems { err = collectionFactory.LocalRepoCollection().LoadComplete(item.localRepo) if err != nil { return @@ -998,13 +1353,10 @@ func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, col } } - item.packageRefs = &PackageRefList{} err = item.packageRefs.Decode(encoded) if err != nil { return } - - repo.sourceItems[component] = item } } else { panic("unknown SourceKind") @@ -1086,7 +1438,7 @@ func (collection *PublishedRepoCollection) ByLocalRepo(repo *LocalRepo) []*Publi // ForEach runs method for each repository func (collection *PublishedRepoCollection) ForEach(handler func(*PublishedRepo) error) error { - return collection.db.ProcessByPrefix([]byte("U"), func(key, blob []byte) error { + return collection.db.ProcessByPrefix([]byte("U"), func(_, blob []byte) error { r := &PublishedRepo{} if err := r.Decode(blob); err != nil { log.Printf("Error decoding published repo: %s\n", err) @@ -1104,21 +1456,13 @@ func (collection *PublishedRepoCollection) Len() int { return len(collection.list) } -// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair -func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix string, components []string, - publishedStorage aptly.PublishedStorage, collectionFactory *CollectionFactory, progress aptly.Progress) error { - - collection.loadList() - - var err error +func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix string, components []string, + collectionFactory *CollectionFactory, progress aptly.Progress) (map[string][]string, error) { referencedFiles := map[string][]string{} - - if progress != nil { - progress.Printf("Cleaning up prefix %#v components %s...\n", prefix, strings.Join(components, ", ")) - } + processedComponentRefs := map[string]*PackageRefList{} for _, r := range collection.list { - if r.Prefix == prefix { + if r.Prefix == prefix && !r.MultiDist { matches := false repoComponents := r.Components() @@ -1134,16 +1478,28 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix st continue } - err = collection.LoadComplete(r, collectionFactory) - if err != nil { - return err + if err := collection.LoadComplete(r, collectionFactory); err != nil { + return nil, err } for _, component := range components { if utils.StrSliceHasItem(repoComponents, component) { - packageList, err := NewPackageListFromRefList(r.RefList(component), collectionFactory.PackageCollection(), progress) + unseenRefs := r.RefList(component) + processedRefs := processedComponentRefs[component] + if processedRefs != nil { + unseenRefs = unseenRefs.Subtract(processedRefs) + } else { + processedRefs = NewPackageRefList() + } + + if unseenRefs.Len() == 0 { + continue + } + processedComponentRefs[component] = processedRefs.Merge(unseenRefs, false, true) + + packageList, err := NewPackageListFromRefList(unseenRefs, collectionFactory.PackageCollection(), progress) if err != nil { - return err + return nil, err } packageList.ForEach(func(p *Package) error { @@ -1163,28 +1519,132 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix st } } - for _, component := range components { + return referencedFiles, nil +} + +// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair +func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(publishedStorageProvider aptly.PublishedStorageProvider, + published *PublishedRepo, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error { + + var err error + + collection.loadList() + + storage := published.Storage + prefix := published.Prefix + distribution := published.Distribution + + rootPath := filepath.Join(prefix, "dists", distribution) + publishedStorage := publishedStorageProvider.GetPublishedStorage(published.Storage) + + sort.Strings(cleanComponents) + publishedComponents := published.Components() + removedComponents := utils.StrSlicesSubstract(cleanComponents, publishedComponents) + updatedComponents := utils.StrSlicesSubstract(cleanComponents, removedComponents) + + if progress != nil { + progress.Printf("Cleaning up published repository %s/%s...\n", published.StoragePrefix(), distribution) + } + + for _, component := range removedComponents { + if progress != nil { + progress.Printf("Removing component '%s'...\n", component) + } + + err = publishedStorage.RemoveDirs(filepath.Join(rootPath, component), progress) + if err != nil { + return err + } + + // Ensure that component does not exist in multi distribution pool + err = publishedStorage.RemoveDirs(filepath.Join(prefix, "pool", distribution, component), nil) + if err != nil { + return err + } + } + + referencedFiles := map[string][]string{} + + if published.MultiDist { + rootPath = filepath.Join(prefix, "pool", distribution) + + // Get all referenced files by component for determining orphaned pool files. + for _, component := range publishedComponents { + packageList, err := NewPackageListFromRefList(published.RefList(component), collectionFactory.PackageCollection(), progress) + if err != nil { + return err + } + + packageList.ForEach(func(p *Package) error { + poolDir, err := p.PoolDirectory() + if err != nil { + return err + } + + for _, file := range p.Files() { + referencedFiles[component] = append(referencedFiles[component], filepath.Join(poolDir, file.Filename)) + } + + return nil + }) + } + } else { + rootPath = filepath.Join(prefix, "pool") + + // In case of a shared component pool directory, we must check, if a component is no longer referenced by any other + // published repository within the same prefix. + referencedComponents := map[string]struct{}{} + for _, p := range collection.list { + if p.Prefix == prefix && p.Storage == storage && !p.MultiDist { + for _, component := range p.Components() { + referencedComponents[component] = struct{}{} + } + } + } + + // Remove orphaned component pool directories in the prefix. + for _, component := range removedComponents { + _, exists := referencedComponents[component] + if !exists { + err := publishedStorage.RemoveDirs(filepath.Join(rootPath, component), progress) + if err != nil { + return err + } + } + } + + // Get all referenced files by component for determining orphaned pool files. + referencedFiles, err = collection.listReferencedFilesByComponent(prefix, publishedComponents, collectionFactory, progress) + if err != nil { + return err + } + } + + for _, component := range updatedComponents { + if progress != nil { + progress.Printf("Cleaning up component '%s'...\n", component) + } sort.Strings(referencedFiles[component]) - rootPath := filepath.Join(prefix, "pool", component) - existingFiles, err := publishedStorage.Filelist(rootPath) + path := filepath.Join(rootPath, component) + existingFiles, err := publishedStorage.Filelist(path) if err != nil { return err } sort.Strings(existingFiles) - filesToDelete := utils.StrSlicesSubstract(existingFiles, referencedFiles[component]) + orphanedFiles := utils.StrSlicesSubstract(existingFiles, referencedFiles[component]) - for _, file := range filesToDelete { - err = publishedStorage.Remove(filepath.Join(rootPath, file)) + for _, file := range orphanedFiles { + err = publishedStorage.Remove(filepath.Join(path, file)) if err != nil { return err } } } - return nil + return err } // Remove removes published repository, cleaning up directories, files @@ -1200,6 +1660,11 @@ func (collection *PublishedRepoCollection) Remove(publishedStorageProvider aptly return err } + err = collection.LoadComplete(repo, collectionFactory) + if err != nil { + return err + } + removePrefix := true removePoolComponents := repo.Components() cleanComponents := []string{} @@ -1235,8 +1700,7 @@ func (collection *PublishedRepoCollection) Remove(publishedStorageProvider aptly nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1] if !skipCleanup && len(cleanComponents) > 0 { - err = collection.CleanupPrefixComponentFiles(repo.Prefix, cleanComponents, - publishedStorageProvider.GetPublishedStorage(storage), collectionFactory, progress) + err = collection.CleanupPrefixComponentFiles(publishedStorageProvider, repo, cleanComponents, collectionFactory, progress) if err != nil { if !force { return fmt.Errorf("cleanup failed, use -force-drop to override: %s", err) diff --git a/deb/publish_bench_test.go b/deb/publish_bench_test.go new file mode 100644 index 00000000..86f18c30 --- /dev/null +++ b/deb/publish_bench_test.go @@ -0,0 +1,113 @@ +package deb + +import ( + "fmt" + "os" + "sort" + "testing" + + "github.com/aptly-dev/aptly/database/goleveldb" +) + +func BenchmarkListReferencedFiles(b *testing.B) { + const defaultComponent = "main" + const repoCount = 16 + const repoPackagesCount = 1024 + const uniqPackagesCount = 64 + + tmpDir, err := os.MkdirTemp("", "aptly-bench") + if err != nil { + b.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + db, err := goleveldb.NewOpenDB(tmpDir) + if err != nil { + b.Fatal(err) + } + defer db.Close() + + factory := NewCollectionFactory(db) + packageCollection := factory.PackageCollection() + repoCollection := factory.LocalRepoCollection() + publishCollection := factory.PublishedRepoCollection() + + sharedRefs := NewPackageRefList() + { + transaction, err := db.OpenTransaction() + if err != nil { + b.Fatal(err) + } + + for pkgIndex := 0; pkgIndex < repoPackagesCount-uniqPackagesCount; pkgIndex++ { + p := &Package{ + Name: fmt.Sprintf("pkg-shared_%d", pkgIndex), + Version: "1", + Architecture: "amd64", + } + p.UpdateFiles(PackageFiles{PackageFile{ + Filename: fmt.Sprintf("pkg-shared_%d.deb", pkgIndex), + }}) + + packageCollection.UpdateInTransaction(p, transaction) + sharedRefs.Refs = append(sharedRefs.Refs, p.Key("")) + } + + sort.Sort(sharedRefs) + + if err := transaction.Commit(); err != nil { + b.Fatal(err) + } + } + + for repoIndex := 0; repoIndex < repoCount; repoIndex++ { + refs := NewPackageRefList() + + transaction, err := db.OpenTransaction() + if err != nil { + b.Fatal(err) + } + + for pkgIndex := 0; pkgIndex < uniqPackagesCount; pkgIndex++ { + p := &Package{ + Name: fmt.Sprintf("pkg%d_%d", repoIndex, pkgIndex), + Version: "1", + Architecture: "amd64", + } + p.UpdateFiles(PackageFiles{PackageFile{ + Filename: fmt.Sprintf("pkg%d_%d.deb", repoIndex, pkgIndex), + }}) + + packageCollection.UpdateInTransaction(p, transaction) + refs.Refs = append(refs.Refs, p.Key("")) + } + + if err := transaction.Commit(); err != nil { + b.Fatal(err) + } + + sort.Sort(refs) + + repo := NewLocalRepo(fmt.Sprintf("repo%d", repoIndex), "comment") + repo.DefaultDistribution = fmt.Sprintf("dist%d", repoIndex) + repo.DefaultComponent = defaultComponent + repo.UpdateRefList(refs.Merge(sharedRefs, false, true)) + repoCollection.Add(repo) + + publish, err := NewPublishedRepo("", "test", "", nil, []string{defaultComponent}, []interface{}{repo}, factory, false) + if err != nil { + b.Fatal(err) + } + publishCollection.Add(publish) + } + + db.CompactDB() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := publishCollection.listReferencedFilesByComponent("test", []string{defaultComponent}, factory, nil) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/deb/publish_test.go b/deb/publish_test.go index 4850720f..c2d228ac 100644 --- a/deb/publish_test.go +++ b/deb/publish_test.go @@ -2,11 +2,13 @@ package deb import ( "bytes" + "encoding/json" "errors" "fmt" "io/ioutil" "os" "path/filepath" + "sort" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/database" @@ -133,19 +135,19 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) { s.packageCollection.Update(s.p2) s.packageCollection.Update(s.p3) - s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory) + s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false) s.repo.SkipContents = true - s.repo2, _ = NewPublishedRepo("", "ppa", "maverick", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory) + s.repo2, _ = NewPublishedRepo("", "ppa", "maverick", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory, false) s.repo2.SkipContents = true - s.repo3, _ = NewPublishedRepo("", "linux", "natty", nil, []string{"main", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory) + s.repo3, _ = NewPublishedRepo("", "linux", "natty", nil, []string{"main", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false) s.repo3.SkipContents = true - s.repo4, _ = NewPublishedRepo("", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory) + s.repo4, _ = NewPublishedRepo("", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false) s.repo4.SkipContents = true - s.repo5, _ = NewPublishedRepo("files:other", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory) + s.repo5, _ = NewPublishedRepo("files:other", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false) s.repo5.SkipContents = true } @@ -177,19 +179,71 @@ func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) { c.Check(s.repo3.RefList("main").Len(), Equals, 3) c.Check(s.repo3.RefList("contrib").Len(), Equals, 3) - c.Check(func() { NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory) }, PanicMatches, "publish with empty sources") + c.Check(func() { NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory, false) }, PanicMatches, "publish with empty sources") c.Check(func() { - NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory) + NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false) }, PanicMatches, "sources and components should be equal in size") c.Check(func() { - NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory) + NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory, false) }, PanicMatches, "interface conversion:.*") - _, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory) + _, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false) c.Check(err, ErrorMatches, "duplicate component name: main") - _, err = NewPublishedRepo("", ".", "wheezy/updates", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory) - c.Check(err, ErrorMatches, "invalid distribution wheezy/updates, '/' is not allowed") + _, err = NewPublishedRepo("", ".", "wheezy/updates", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false) + c.Check(err, IsNil) +} + +func (s *PublishedRepoSuite) TestMultiDistPool(c *C) { + repo, err := NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, true) + c.Assert(err, IsNil) + err = repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "") + c.Assert(err, IsNil) + + publishedStorage := files.NewPublishedStorage(s.root, "", "") + + c.Check(repo.Architectures, DeepEquals, []string{"i386"}) + + rf, err := os.Open(filepath.Join(publishedStorage.PublicPath(), "ppa/dists/squeeze/Release")) + c.Assert(err, IsNil) + + cfr := NewControlFileReader(rf, true, false) + st, err := cfr.ReadStanza() + c.Assert(err, IsNil) + + c.Check(st["Origin"], Equals, "ppa squeeze") + c.Check(st["Components"], Equals, "main") + c.Check(st["Architectures"], Equals, "i386") + + pf, err := os.Open(filepath.Join(publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Packages")) + c.Assert(err, IsNil) + + cfr = NewControlFileReader(pf, false, false) + + for i := 0; i < 3; i++ { + st, err = cfr.ReadStanza() + c.Assert(err, IsNil) + + c.Check(st["Filename"], Equals, "pool/squeeze/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb") + } + + st, err = cfr.ReadStanza() + c.Assert(err, IsNil) + c.Assert(st, IsNil) + + drf, err := os.Open(filepath.Join(publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release")) + c.Assert(err, IsNil) + + cfr = NewControlFileReader(drf, true, false) + st, err = cfr.ReadStanza() + c.Assert(err, IsNil) + + c.Check(st["Archive"], Equals, "squeeze") + c.Check(st["Architecture"], Equals, "i386") + + _, err = os.Stat(filepath.Join(publishedStorage.PublicPath(), "ppa/pool/squeeze/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb")) + c.Assert(err, IsNil) + } func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) { @@ -248,7 +302,7 @@ func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) { errorExpected: "invalid prefix .*", }, } { - repo, err := NewPublishedRepo("", t.prefix, "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory) + repo, err := NewPublishedRepo("", t.prefix, "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false) if t.errorExpected != "" { c.Check(err, ErrorMatches, t.errorExpected) } else { @@ -258,56 +312,75 @@ func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) { } func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) { - repo, err := NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.snapshot}, s.factory) + repo, err := NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.snapshot}, s.factory, false) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "squeeze") c.Check(repo.Components(), DeepEquals, []string{"main"}) - repo, err = NewPublishedRepo("", "ppa", "wheezy", nil, []string{""}, []interface{}{s.snapshot}, s.factory) + repo, err = NewPublishedRepo("", "ppa", "wheezy", nil, []string{""}, []interface{}{s.snapshot}, s.factory, false) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "wheezy") c.Check(repo.Components(), DeepEquals, []string{"main"}) - repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"non-free"}, []interface{}{s.snapshot}, s.factory) + repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"non-free"}, []interface{}{s.snapshot}, s.factory, false) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "squeeze") c.Check(repo.Components(), DeepEquals, []string{"non-free"}) - repo, err = NewPublishedRepo("", "ppa", "squeeze", nil, []string{""}, []interface{}{s.localRepo}, s.factory) + repo, err = NewPublishedRepo("", "ppa", "squeeze", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "squeeze") c.Check(repo.Components(), DeepEquals, []string{"main"}) - _, err = NewPublishedRepo("", "ppa", "", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory) + _, err = NewPublishedRepo("", "ppa", "", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory, false) c.Check(err, ErrorMatches, "unable to guess distribution name, please specify explicitly") s.localRepo.DefaultDistribution = "precise" s.localRepo.DefaultComponent = "contrib" s.factory.LocalRepoCollection().Update(s.localRepo) - repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory) + repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "precise") c.Check(repo.Components(), DeepEquals, []string{"contrib"}) s.localRepo.DefaultDistribution = "precise/updates" - repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory) + repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false) c.Check(err, IsNil) - c.Check(repo.Distribution, Equals, "precise-updates") + c.Check(repo.Distribution, Equals, "precise/updates") c.Check(repo.Components(), DeepEquals, []string{"contrib"}) - repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory) + repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "squeeze") c.Check(repo.Components(), DeepEquals, []string{"contrib", "main"}) - _, err = NewPublishedRepo("", "ppa", "", nil, []string{"", ""}, []interface{}{s.snapshot, s.snapshot2}, s.factory) + _, err = NewPublishedRepo("", "ppa", "", nil, []string{"", ""}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false) c.Check(err, ErrorMatches, "duplicate component name: main") } +func (s *PublishedRepoSuite) TestUpdate(c *C) { + revision := s.repo2.ObtainRevision() + sources := revision.Sources + sources["test"] = "local1" + + result, err := s.repo2.Update(s.factory, nil) + c.Assert(err, IsNil) + c.Assert(result, NotNil) + c.Assert(s.repo2.Revision, IsNil) + + c.Assert(result.AddedSources, DeepEquals, map[string]string{"test": "local1"}) + c.Assert(result.UpdatedSources, DeepEquals, map[string]string{"main": "local1"}) + c.Assert(result.RemovedSources, DeepEquals, map[string]string{}) + + c.Assert(result.AddedComponents(), DeepEquals, []string{"test"}) + c.Assert(result.UpdatedComponents(), DeepEquals, []string{"main"}) + c.Assert(result.RemovedComponents(), DeepEquals, []string{}) +} + func (s *PublishedRepoSuite) TestPublish(c *C) { - err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false) + err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "") c.Assert(err, IsNil) c.Check(s.repo.Architectures, DeepEquals, []string{"i386"}) @@ -354,7 +427,7 @@ func (s *PublishedRepoSuite) TestPublish(c *C) { } func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) { - err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false) + err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "") c.Assert(err, IsNil) c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"), PathExists) @@ -362,7 +435,7 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) { } func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) { - err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false) + err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "") c.Assert(err, IsNil) c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), PathExists) @@ -370,7 +443,7 @@ func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) { } func (s *PublishedRepoSuite) TestPublishLocalSourceRepo(c *C) { - err := s.repo4.Publish(s.packagePool, s.provider, s.factory, nil, nil, false) + err := s.repo4.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "") c.Assert(err, IsNil) c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), PathExists) @@ -378,7 +451,7 @@ func (s *PublishedRepoSuite) TestPublishLocalSourceRepo(c *C) { } func (s *PublishedRepoSuite) TestPublishOtherStorage(c *C) { - err := s.repo5.Publish(s.packagePool, s.provider, s.factory, nil, nil, false) + err := s.repo5.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "") c.Assert(err, IsNil) c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/maverick/Release"), PathExists) @@ -390,10 +463,10 @@ func (s *PublishedRepoSuite) TestString(c *C) { "ppa/squeeze [] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}") c.Check(s.repo2.String(), Equals, "ppa/maverick [] publishes {main: [local1]: comment1}") - repo, _ := NewPublishedRepo("", "", "squeeze", []string{"s390"}, []string{"main"}, []interface{}{s.snapshot}, s.factory) + repo, _ := NewPublishedRepo("", "", "squeeze", []string{"s390"}, []string{"main"}, []interface{}{s.snapshot}, s.factory, false) c.Check(repo.String(), Equals, "./squeeze [s390] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}") - repo, _ = NewPublishedRepo("", "", "squeeze", []string{"i386", "amd64"}, []string{"main"}, []interface{}{s.snapshot}, s.factory) + repo, _ = NewPublishedRepo("", "", "squeeze", []string{"i386", "amd64"}, []string{"main"}, []interface{}{s.snapshot}, s.factory, false) c.Check(repo.String(), Equals, "./squeeze [i386, amd64] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}") repo.Origin = "myorigin" @@ -436,6 +509,30 @@ func (s *PublishedRepoSuite) TestEncodeDecode(c *C) { c.Assert(repo2, DeepEquals, s.repo2) } +func (s *PublishedRepoSuite) TestPublishedRepoRevision(c *C) { + revision := s.repo2.ObtainRevision() + c.Assert(revision, NotNil) + + sources := revision.Sources + c.Assert(sources, NotNil) + c.Assert(sources, DeepEquals, map[string]string{"main": "local1"}) + + sources["test1"] = "snap1" + sources["test2"] = "snap2" + + c.Assert(revision.Components(), DeepEquals, []string{"main", "test1", "test2"}) + c.Assert(revision.SourceNames(), DeepEquals, []string{"local1", "snap1", "snap2"}) + + bytes, err := json.Marshal(revision) + c.Assert(err, IsNil) + + json_expected := `{"Sources":[{"Component":"main","Name":"local1"},{"Component":"test1","Name":"snap1"},{"Component":"test2","Name":"snap2"}]}` + c.Assert(string(bytes), Equals, json_expected) + + c.Assert(s.repo2.DropRevision(), DeepEquals, revision) + c.Assert(s.repo2.Revision, IsNil) +} + type PublishedRepoCollectionSuite struct { PackageListMixinSuite db database.Storage @@ -450,13 +547,22 @@ type PublishedRepoCollectionSuite struct { var _ = Suite(&PublishedRepoCollectionSuite{}) func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) { + s.SetUpPackages() + s.db, _ = goleveldb.NewOpenDB(c.MkDir()) s.factory = NewCollectionFactory(s.db) s.snapshotCollection = s.factory.SnapshotCollection() - s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1") - s.snap2 = NewSnapshotFromPackageList("snap2", []*Snapshot{}, NewPackageList(), "desc2") + snap1Refs := NewPackageRefList() + snap1Refs.Refs = [][]byte{s.p1.Key(""), s.p2.Key("")} + sort.Sort(snap1Refs) + s.snap1 = NewSnapshotFromRefList("snap1", []*Snapshot{}, snap1Refs, "desc1") + + snap2Refs := NewPackageRefList() + snap2Refs.Refs = [][]byte{s.p3.Key("")} + sort.Sort(snap2Refs) + s.snap2 = NewSnapshotFromRefList("snap2", []*Snapshot{}, snap2Refs, "desc2") s.snapshotCollection.Add(s.snap1) s.snapshotCollection.Add(s.snap2) @@ -464,11 +570,11 @@ func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) { s.localRepo = NewLocalRepo("local1", "comment1") s.factory.LocalRepoCollection().Add(s.localRepo) - s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory) - s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main", "contrib"}, []interface{}{s.snap2, s.snap1}, s.factory) - s.repo3, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap2}, s.factory) - s.repo4, _ = NewPublishedRepo("", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory) - s.repo5, _ = NewPublishedRepo("files:other", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory) + s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false) + s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main", "contrib"}, []interface{}{s.snap2, s.snap1}, s.factory, false) + s.repo3, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap2}, s.factory, false) + s.repo4, _ = NewPublishedRepo("", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false) + s.repo5, _ = NewPublishedRepo("files:other", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false) s.collection = s.factory.PublishedRepoCollection() } @@ -534,7 +640,7 @@ func (s *PublishedRepoCollectionSuite) TestUpdateLoadComplete(c *C) { c.Assert(r.sourceItems["main"].snapshot, IsNil) c.Assert(s.collection.LoadComplete(r, s.factory), IsNil) c.Assert(r.Sources["main"], Equals, s.repo1.sourceItems["main"].snapshot.UUID) - c.Assert(r.RefList("main").Len(), Equals, 0) + c.Assert(r.RefList("main").Len(), Equals, 2) r, err = collection.ByStoragePrefixDistribution("", "ppa", "precise") c.Assert(err, IsNil) @@ -625,6 +731,51 @@ func (s *PublishedRepoCollectionSuite) TestByLocalRepo(c *C) { c.Check(s.collection.ByLocalRepo(s.localRepo), DeepEquals, []*PublishedRepo{s.repo4, s.repo5}) } +func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) { + c.Check(s.factory.PackageCollection().Update(s.p1), IsNil) + c.Check(s.factory.PackageCollection().Update(s.p2), IsNil) + c.Check(s.factory.PackageCollection().Update(s.p3), IsNil) + + c.Check(s.collection.Add(s.repo1), IsNil) + c.Check(s.collection.Add(s.repo2), IsNil) + c.Check(s.collection.Add(s.repo4), IsNil) + c.Check(s.collection.Add(s.repo5), IsNil) + + files, err := s.collection.listReferencedFilesByComponent(".", []string{"main", "contrib"}, s.factory, nil) + c.Assert(err, IsNil) + for _, v := range files { + sort.Strings(v) + } + c.Check(files, DeepEquals, map[string][]string{ + "contrib": { + "a/alien-arena/alien-arena-common_7.40-2_i386.deb", + "a/alien-arena/mars-invaders_7.40-2_i386.deb", + }, + "main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"}, + }) + + snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3") + s.snapshotCollection.Add(snap3) + + // Ensure that adding a second publish point with matching files doesn't give duplicate results. + repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false) + c.Check(err, IsNil) + c.Check(s.collection.Add(repo3), IsNil) + + files, err = s.collection.listReferencedFilesByComponent(".", []string{"main", "contrib"}, s.factory, nil) + c.Assert(err, IsNil) + for _, v := range files { + sort.Strings(v) + } + c.Check(files, DeepEquals, map[string][]string{ + "contrib": { + "a/alien-arena/alien-arena-common_7.40-2_i386.deb", + "a/alien-arena/mars-invaders_7.40-2_i386.deb", + }, + "main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"}, + }) +} + type PublishedRepoRemoveSuite struct { PackageListMixinSuite db database.Storage @@ -650,11 +801,11 @@ func (s *PublishedRepoRemoveSuite) SetUpTest(c *C) { s.snapshotCollection.Add(s.snap1) - s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory) - s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory) - s.repo3, _ = NewPublishedRepo("", "ppa", "meduza", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory) - s.repo4, _ = NewPublishedRepo("", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory) - s.repo5, _ = NewPublishedRepo("files:other", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory) + s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false) + s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false) + s.repo3, _ = NewPublishedRepo("", "ppa", "meduza", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false) + s.repo4, _ = NewPublishedRepo("", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory, false) + s.repo5, _ = NewPublishedRepo("files:other", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory, false) s.collection = s.factory.PublishedRepoCollection() s.collection.Add(s.repo1) diff --git a/deb/query.go b/deb/query.go index 19a1cdc9..ad0ad17a 100644 --- a/deb/query.go +++ b/deb/query.go @@ -20,7 +20,7 @@ type PackageLike interface { // PackageCatalog is abstraction on top of PackageCollection and PackageList type PackageCatalog interface { Scan(q PackageQuery) (result *PackageList) - Search(dep Dependency, allMatches bool) (searchResults []*Package) + Search(dep Dependency, allMatches bool, searchProvided bool) (searchResults []*Package) SearchSupported() bool SearchByKey(arch, name, version string) (result *PackageList) } @@ -138,7 +138,7 @@ func (q *NotQuery) Matches(pkg PackageLike) bool { } // Fast is false -func (q *NotQuery) Fast(list PackageCatalog) bool { +func (q *NotQuery) Fast(_ PackageCatalog) bool { return false } @@ -197,7 +197,7 @@ func (q *FieldQuery) Query(list PackageCatalog) (result *PackageList) { } // Fast depends on the query -func (q *FieldQuery) Fast(list PackageCatalog) bool { +func (q *FieldQuery) Fast(_ PackageCatalog) bool { return false } @@ -244,7 +244,7 @@ func (q *DependencyQuery) Fast(list PackageCatalog) bool { func (q *DependencyQuery) Query(list PackageCatalog) (result *PackageList) { if q.Fast(list) { result = NewPackageList() - for _, pkg := range list.Search(q.Dep, true) { + for _, pkg := range list.Search(q.Dep, true, true) { result.Add(pkg) } } else { @@ -265,7 +265,7 @@ func (q *PkgQuery) Matches(pkg PackageLike) bool { } // Fast is always true for package query -func (q *PkgQuery) Fast(list PackageCatalog) bool { +func (q *PkgQuery) Fast(_ PackageCatalog) bool { return true } @@ -280,12 +280,12 @@ func (q *PkgQuery) String() string { } // Matches on specific properties -func (q *MatchAllQuery) Matches(pkg PackageLike) bool { +func (q *MatchAllQuery) Matches(_ PackageLike) bool { return true } // Fast is always true for match all query -func (q *MatchAllQuery) Fast(list PackageCatalog) bool { +func (q *MatchAllQuery) Fast(_ PackageCatalog) bool { return true } diff --git a/deb/reflist.go b/deb/reflist.go index 187475dd..30396548 100644 --- a/deb/reflist.go +++ b/deb/reflist.go @@ -71,7 +71,9 @@ func (l *PackageRefList) Encode() []byte { // Decode decodes msgpack representation into PackageRefLit func (l *PackageRefList) Decode(input []byte) error { - decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{}) + handle := &codec.MsgpackHandle{} + handle.ZeroCopy = true + decoder := codec.NewDecoderBytes(input, handle) return decoder.Decode(l) } @@ -194,31 +196,21 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle // until we reached end of both lists for il < ll || ir < lr { - // if we've exhausted left list, pull the rest from the right - if il == ll { - pr, err = packageCollection.ByKey(r.Refs[ir]) - if err != nil { - return nil, err - } - result = append(result, PackageDiff{Left: nil, Right: pr}) - ir++ - continue + var rl, rr []byte + if il < ll { + rl = l.Refs[il] } - // if we've exhausted right list, pull the rest from the left - if ir == lr { - pl, err = packageCollection.ByKey(l.Refs[il]) - if err != nil { - return nil, err - } - result = append(result, PackageDiff{Left: pl, Right: nil}) - il++ - continue + if ir < lr { + rr = r.Refs[ir] } - // refs on both sides are present, load them - rl, rr := l.Refs[il], r.Refs[ir] // compare refs rel := bytes.Compare(rl, rr) + // an unset ref is less than all others, but since it represents the end + // of a reflist, it should be *greater*, so flip the comparison result + if rl == nil || rr == nil { + rel = -rel + } if rel == 0 { // refs are identical, so are packages, advance pointer @@ -227,14 +219,14 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle pl, pr = nil, nil } else { // load pl & pr if they haven't been loaded before - if pl == nil { + if pl == nil && rl != nil { pl, err = packageCollection.ByKey(rl) if err != nil { return nil, err } } - if pr == nil { + if pr == nil && rr != nil { pr, err = packageCollection.ByKey(rr) if err != nil { return nil, err @@ -310,38 +302,41 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching, ignoreConfli overridenName = nil overriddenArch = nil } else { - partsL := bytes.Split(rl, []byte(" ")) - archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2] + if !ignoreConflicting || overrideMatching { + partsL := bytes.Split(rl, []byte(" ")) + archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2] - partsR := bytes.Split(rr, []byte(" ")) - archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2] + partsR := bytes.Split(rr, []byte(" ")) + archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2] - if !ignoreConflicting && bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) { - // conflicting duplicates with same arch, name, version, but different file hash - result.Refs = append(result.Refs, r.Refs[ir]) - il++ - ir++ - overridenName = nil - overriddenArch = nil - continue - } - - if overrideMatching { - if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) { - // this package has already been overridden on the right - il++ - continue - } - - if bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) { - // override with package from the right + if !ignoreConflicting && bytes.Equal(archL, archR) && + bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) { + // conflicting duplicates with same arch, name, version, but different file hash result.Refs = append(result.Refs, r.Refs[ir]) il++ ir++ - overriddenArch = archL - overridenName = nameL + overridenName = nil + overriddenArch = nil continue } + + if overrideMatching { + if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) { + // this package has already been overridden on the right + il++ + continue + } + + if bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) { + // override with package from the right + result.Refs = append(result.Refs, r.Refs[ir]) + il++ + ir++ + overriddenArch = archL + overridenName = nameL + continue + } + } } // otherwise append smallest of two diff --git a/deb/reflist_bench_test.go b/deb/reflist_bench_test.go new file mode 100644 index 00000000..b377574c --- /dev/null +++ b/deb/reflist_bench_test.go @@ -0,0 +1,47 @@ +package deb + +import ( + "fmt" + "sort" + "testing" +) + +func BenchmarkReflistSimpleMerge(b *testing.B) { + const count = 4096 + + l := NewPackageRefList() + r := NewPackageRefList() + + for i := 0; i < count; i++ { + if i%2 == 0 { + l.Refs = append(l.Refs, []byte(fmt.Sprintf("Pamd64 pkg%d %d", i, i))) + } else { + r.Refs = append(r.Refs, []byte(fmt.Sprintf("Pamd64 pkg%d %d", i, i))) + } + } + + sort.Sort(l) + sort.Sort(r) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Merge(r, false, true) + } +} + +func BenchmarkReflistDecode(b *testing.B) { + const count = 4096 + + r := NewPackageRefList() + for i := 0; i < count; i++ { + r.Refs = append(r.Refs, []byte(fmt.Sprintf("Pamd64 pkg%d %d", i, i))) + } + + sort.Sort(r) + data := r.Encode() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + (&PackageRefList{}).Decode(data) + } +} diff --git a/deb/reflist_test.go b/deb/reflist_test.go index d0ce21f5..bcabec3c 100644 --- a/deb/reflist_test.go +++ b/deb/reflist_test.go @@ -65,7 +65,7 @@ func (s *PackageRefListSuite) TestNewPackageListFromRefList(c *C) { list, err := NewPackageListFromRefList(reflist, coll, nil) c.Assert(err, IsNil) c.Check(list.Len(), Equals, 4) - c.Check(list.Add(s.p4), ErrorMatches, "conflict in package.*") + c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*") list, err = NewPackageListFromRefList(nil, coll, nil) c.Assert(err, IsNil) @@ -237,6 +237,41 @@ func (s *PackageRefListSuite) TestDiff(c *C) { } +func (s *PackageRefListSuite) TestDiffCompactsAtEnd(c *C) { + db, _ := goleveldb.NewOpenDB(c.MkDir()) + coll := NewPackageCollection(db) + + packages := []*Package{ + {Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //0 + {Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //1 + {Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //2 + } + + for _, p := range packages { + coll.Update(p) + } + + listA := NewPackageList() + listA.Add(packages[0]) + + listB := NewPackageList() + listB.Add(packages[1]) + listB.Add(packages[2]) + + reflistA := NewPackageRefListFromPackageList(listA) + reflistB := NewPackageRefListFromPackageList(listB) + + diffAB, err := reflistA.Diff(reflistB, coll) + c.Check(err, IsNil) + c.Check(diffAB, HasLen, 2) + + c.Check(diffAB[0].Left, IsNil) + c.Check(diffAB[0].Right.String(), Equals, "app_1.1~bp2_amd64") + + c.Check(diffAB[1].Left.String(), Equals, "app_1.1~bp1_i386") + c.Check(diffAB[1].Right.String(), Equals, "app_1.1~bp2_i386") +} + func (s *PackageRefListSuite) TestMerge(c *C) { db, _ := goleveldb.NewOpenDB(c.MkDir()) coll := NewPackageCollection(db) diff --git a/deb/remote.go b/deb/remote.go index 6d30e999..9f89f905 100644 --- a/deb/remote.go +++ b/deb/remote.go @@ -105,7 +105,6 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone if !strings.HasPrefix(result.Distribution, ".") { result.Distribution = "./" + result.Distribution } - result.Architectures = nil if len(result.Components) > 0 { return nil, fmt.Errorf("components aren't supported for flat repos") } @@ -259,6 +258,9 @@ func (repo *RemoteRepo) UdebPath(component string, architecture string) string { // InstallerPath returns path of Packages files for given component and // architecture func (repo *RemoteRepo) InstallerPath(component string, architecture string) string { + if repo.Distribution == aptly.DistributionFocal { + return fmt.Sprintf("%s/installer-%s/current/legacy-images/SHA256SUMS", component, architecture) + } return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture) } @@ -270,17 +272,29 @@ func (repo *RemoteRepo) PackageURL(filename string) *url.URL { } // Fetch updates information about repository -func (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier pgp.Verifier) error { +func (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier pgp.Verifier, ignoreSignatures bool) error { var ( release, inrelease, releasesig *os.File err error ) - if verifier == nil { + if ignoreSignatures { // 0. Just download release file to temporary URL release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String()) if err != nil { - return err + // 0.1 try downloading InRelease, ignore and strip signature + inrelease, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("InRelease").String()) + if err != nil { + return err + } + if verifier == nil { + return fmt.Errorf("no verifier specified") + } + release, err = verifier.ExtractClearsigned(inrelease) + if err != nil { + return err + } + goto ok } } else { // 1. try InRelease file @@ -336,7 +350,7 @@ ok: return err } - if !repo.IsFlat() { + if len(stanza["Architectures"]) > 0 { architectures := strings.Split(stanza["Architectures"], " ") sort.Strings(architectures) // "source" architecture is never present, despite Release file claims @@ -350,7 +364,13 @@ ok: return err } } + } + if len(repo.Architectures) == 0 { + return fmt.Errorf("no architectures found, please specify") + } + + if !repo.IsFlat() { components := strings.Split(stanza["Components"], " ") if strings.Contains(repo.Distribution, "/") { distributionLast := path.Base(repo.Distribution) + "/" @@ -428,8 +448,7 @@ ok: } // DownloadPackageIndexes downloads & parses package index files -func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, verifier pgp.Verifier, collectionFactory *CollectionFactory, - ignoreMismatch bool) error { +func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, verifier pgp.Verifier, _ *CollectionFactory, ignoreSignatures bool, ignoreChecksums bool) error { if repo.packageList != nil { panic("packageList != nil") } @@ -462,14 +481,14 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly. for _, info := range packagesPaths { path, kind, component, architecture := info[0], info[1], info[2], info[3] - packagesReader, packagesFile, err := http.DownloadTryCompression(gocontext.TODO(), d, repo.IndexesRootURL(), path, repo.ReleaseFiles, ignoreMismatch) + packagesReader, packagesFile, err := http.DownloadTryCompression(gocontext.TODO(), d, repo.IndexesRootURL(), path, repo.ReleaseFiles, ignoreChecksums) isInstaller := kind == PackageTypeInstaller if err != nil { if _, ok := err.(*http.NoCandidateFoundError); isInstaller && ok { // checking if gpg file is only needed when checksums matches are required. // otherwise there actually has been no candidate found and we can continue - if ignoreMismatch { + if ignoreChecksums { continue } @@ -486,7 +505,7 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly. return err } - if verifier != nil { + if verifier != nil && !ignoreSignatures { hashsumGpgPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path + ".gpg"}).String() var filesig *os.File filesig, err = http.DownloadTemp(gocontext.TODO(), d, hashsumGpgPath) @@ -577,7 +596,15 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu emptyList.PrepareIndex() oldLen = repo.packageList.Len() - repo.packageList, err = repo.packageList.FilterWithProgress([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures, progress) + repo.packageList, err = repo.packageList.Filter(FilterOptions{ + Queries: []PackageQuery{filterQuery}, + WithDependencies: repo.FilterWithDeps, + Source: emptyList, + WithSources: repo.DownloadSources, + DependencyOptions: dependencyOptions, + Architectures: repo.Architectures, + Progress: progress, + }) if repo.packageList != nil { newLen = repo.packageList.Len() } @@ -777,7 +804,7 @@ func (collection *RemoteRepoCollection) search(filter func(*RemoteRepo) bool, un return result } - collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error { + collection.db.ProcessByPrefix([]byte("R"), func(_, blob []byte) error { r := &RemoteRepo{} if err := r.Decode(blob); err != nil { log.Printf("Error decoding remote repo: %s\n", err) @@ -880,7 +907,7 @@ func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) // ForEach runs method for each repository func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error) error { - return collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error { + return collection.db.ProcessByPrefix([]byte("R"), func(_, blob []byte) error { r := &RemoteRepo{} if err := r.Decode(blob); err != nil { log.Printf("Error decoding mirror: %s\n", err) diff --git a/deb/remote_test.go b/deb/remote_test.go index 1ea1d558..1998db7e 100644 --- a/deb/remote_test.go +++ b/deb/remote_test.go @@ -3,7 +3,6 @@ package deb import ( "errors" "io" - "io/ioutil" "os" "sort" @@ -22,7 +21,7 @@ import ( type NullVerifier struct { } -func (n *NullVerifier) InitKeyring() error { +func (n *NullVerifier) InitKeyring(_ bool) error { return nil } @@ -38,7 +37,7 @@ func (n *NullVerifier) VerifyClearsigned(clearsigned io.Reader, hint bool) (*pgp } func (n *NullVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, err error) { - text, _ = ioutil.TempFile("", "aptly-test") + text, _ = os.CreateTemp("", "aptly-test") io.Copy(text, clearsigned) text.Seek(0, 0) os.Remove(text.Name()) @@ -62,9 +61,11 @@ func (s *PackageListMixinSuite) SetUpPackages() { s.p1 = NewPackageFromControlFile(packageStanza.Copy()) stanza := packageStanza.Copy() stanza["Package"] = "mars-invaders" + stanza["Filename"] = "pool/contrib/m/mars-invaders/mars-invaders_7.40-2_i386.deb" s.p2 = NewPackageFromControlFile(stanza) stanza = packageStanza.Copy() stanza["Package"] = "lonely-strangers" + stanza["Filename"] = "pool/contrib/l/lonely-strangers/lonely-strangers_7.40-2_i386.deb" s.p3 = NewPackageFromControlFile(stanza) s.list.Add(s.p1) @@ -92,7 +93,7 @@ func (s *RemoteRepoSuite) SetUpTest(c *C) { s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false, false) s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false, false) s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) - s.progress = console.NewProgress() + s.progress = console.NewProgress(false) s.db, _ = goleveldb.NewOpenDB(c.MkDir()) s.collectionFactory = NewCollectionFactory(s.db) s.packagePool = files.NewPackagePool(c.MkDir(), false) @@ -114,7 +115,6 @@ func (s *RemoteRepoSuite) TestInvalidURL(c *C) { func (s *RemoteRepoSuite) TestFlatCreation(c *C) { c.Check(s.flat.IsFlat(), Equals, true) c.Check(s.flat.Distribution, Equals, "./") - c.Check(s.flat.Architectures, IsNil) c.Check(s.flat.Components, IsNil) flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false, false) @@ -196,7 +196,7 @@ func (s *RemoteRepoSuite) TestPackageURL(c *C) { } func (s *RemoteRepoSuite) TestFetch(c *C) { - err := s.repo.Fetch(s.downloader, nil) + err := s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) c.Assert(s.repo.Architectures, DeepEquals, []string{"amd64", "armel", "armhf", "i386", "powerpc"}) c.Assert(s.repo.Components, DeepEquals, []string{"main"}) @@ -217,7 +217,7 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier1(c *C) { downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release.gpg", "GPG") - err := s.repo.Fetch(downloader, &NullVerifier{}) + err := s.repo.Fetch(downloader, &NullVerifier{}, false) c.Assert(err, IsNil) c.Assert(s.repo.Architectures, DeepEquals, []string{"amd64", "armel", "armhf", "i386", "powerpc"}) c.Assert(s.repo.Components, DeepEquals, []string{"main"}) @@ -228,7 +228,7 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) { downloader := http.NewFakeDownloader() downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", exampleReleaseFile) - err := s.repo.Fetch(downloader, &NullVerifier{}) + err := s.repo.Fetch(downloader, &NullVerifier{}, false) c.Assert(err, IsNil) c.Assert(s.repo.Architectures, DeepEquals, []string{"amd64", "armel", "armhf", "i386", "powerpc"}) c.Assert(s.repo.Components, DeepEquals, []string{"main"}) @@ -237,13 +237,13 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) { func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) { s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false, false) - err := s.repo.Fetch(s.downloader, nil) + err := s.repo.Fetch(s.downloader, nil, true) c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*") } func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) { s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false, false) - err := s.repo.Fetch(s.downloader, nil) + err := s.repo.Fetch(s.downloader, nil, true) c.Assert(err, ErrorMatches, "component xyz not available in repo.*") } @@ -270,14 +270,14 @@ func (s *RemoteRepoSuite) TestRefKey(c *C) { func (s *RemoteRepoSuite) TestDownload(c *C) { s.repo.Architectures = []string{"i386"} - err := s.repo.Fetch(s.downloader, nil) + err := s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.Error{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -297,14 +297,14 @@ func (s *RemoteRepoSuite) TestDownload(c *C) { // Next call must return an empty download list with option "skip-existing-packages" s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) - err = s.repo.Fetch(s.downloader, nil) + err = s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.Error{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -318,14 +318,14 @@ func (s *RemoteRepoSuite) TestDownload(c *C) { // Next call must return the download list without option "skip-existing-packages" s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) - err = s.repo.Fetch(s.downloader, nil) + err = s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.Error{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -343,7 +343,7 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) { s.repo.Architectures = []string{"i386"} s.repo.DownloadInstaller = true - err := s.repo.Fetch(s.downloader, nil) + err := s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) @@ -352,7 +352,7 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) { s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/installer-i386/current/images/SHA256SUMS", exampleInstallerHashSumFile) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/installer-i386/current/images/MANIFEST", exampleInstallerManifestFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -386,7 +386,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { s.repo.Architectures = []string{"i386"} s.repo.DownloadSources = true - err := s.repo.Fetch(s.downloader, nil) + err := s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) @@ -396,7 +396,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", &http.Error{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -430,7 +430,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { // Next call must return an empty download list with option "skip-existing-packages" s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) - err = s.repo.Fetch(s.downloader, nil) + err = s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) @@ -440,7 +440,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", &http.Error{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -455,7 +455,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { // Next call must return the download list without option "skip-existing-packages" s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) - err = s.repo.Fetch(s.downloader, nil) + err = s.repo.Fetch(s.downloader, nil, true) c.Assert(err, IsNil) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404}) @@ -465,7 +465,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", &http.Error{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile) - err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, false) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) @@ -486,10 +486,10 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.xz", &http.Error{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) - err := s.flat.Fetch(downloader, nil) + err := s.flat.Fetch(downloader, nil, true) c.Assert(err, IsNil) - err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true, true) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) @@ -514,10 +514,10 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.xz", &http.Error{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) - err = s.flat.Fetch(downloader, nil) + err = s.flat.Fetch(downloader, nil, true) c.Assert(err, IsNil) - err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true, true) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) @@ -536,10 +536,10 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.xz", &http.Error{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) - err = s.flat.Fetch(downloader, nil) + err = s.flat.Fetch(downloader, nil, true) c.Assert(err, IsNil) - err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true, true) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) @@ -567,10 +567,10 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.xz", &http.Error{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile) - err := s.flat.Fetch(downloader, nil) + err := s.flat.Fetch(downloader, nil, true) c.Assert(err, IsNil) - err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true, true) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) @@ -613,10 +613,10 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.xz", &http.Error{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile) - err = s.flat.Fetch(downloader, nil) + err = s.flat.Fetch(downloader, nil, true) c.Assert(err, IsNil) - err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true, true) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) @@ -639,10 +639,10 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.xz", &http.Error{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile) - err = s.flat.Fetch(downloader, nil) + err = s.flat.Fetch(downloader, nil, true) c.Assert(err, IsNil) - err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, nil, s.collectionFactory, true, true) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) diff --git a/deb/snapshot.go b/deb/snapshot.go index f351b87f..f2a0d387 100644 --- a/deb/snapshot.go +++ b/deb/snapshot.go @@ -45,7 +45,7 @@ type Snapshot struct { // NewSnapshotFromRepository creates snapshot from current state of repository func NewSnapshotFromRepository(name string, repo *RemoteRepo) (*Snapshot, error) { - if repo.packageRefs == nil { + if repo.packageRefs == nil || repo.packageRefs.Len() == 0 { return nil, errors.New("mirror not updated") } @@ -259,7 +259,7 @@ func (collection *SnapshotCollection) search(filter func(*Snapshot) bool, unique return result } - collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error { + collection.db.ProcessByPrefix([]byte("S"), func(_, blob []byte) error { s := &Snapshot{} if err := s.Decode(blob); err != nil { log.Printf("Error decoding snapshot: %s\n", err) @@ -341,7 +341,7 @@ func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Sn // ForEach runs method for each snapshot func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) error { - return collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error { + return collection.db.ProcessByPrefix([]byte("S"), func(_, blob []byte) error { s := &Snapshot{} if err := s.Decode(blob); err != nil { log.Printf("Error decoding snapshot: %s\n", err) diff --git a/debian/aptly-api.default b/debian/aptly-api.default new file mode 100644 index 00000000..5950dde4 --- /dev/null +++ b/debian/aptly-api.default @@ -0,0 +1,8 @@ +# Default settings for aptly-api systemd service + +# bind to host:port +LISTEN_ADDRESS='localhost:8080' + +# aptly options: +# -no-lock allow aptly commands in parallel with api service (no global database lock, but lock per request) +APTLY_OPTIONS="-no-lock" diff --git a/debian/aptly-api.install b/debian/aptly-api.install new file mode 100644 index 00000000..05655571 --- /dev/null +++ b/debian/aptly-api.install @@ -0,0 +1 @@ +debian/aptly.conf etc/ diff --git a/debian/aptly-api.maintscript b/debian/aptly-api.maintscript new file mode 100644 index 00000000..dbc52ad4 --- /dev/null +++ b/debian/aptly-api.maintscript @@ -0,0 +1 @@ +mv_conffile /etc/aptly-api.conf /etc/aptly.conf 1.6~ diff --git a/debian/aptly-api.postinst b/debian/aptly-api.postinst new file mode 100755 index 00000000..01edd3a1 --- /dev/null +++ b/debian/aptly-api.postinst @@ -0,0 +1,31 @@ +#!/bin/sh + +set -e + +# source debconf library +. /usr/share/debconf/confmodule + +case "$1" in + configure|reconfigure) + # create an aptly-api group and user + if ! getent passwd aptly-api > /dev/null; then + useradd --system --user-group --create-home --home-dir /var/lib/aptly-api aptly-api + fi + + # set config file permissions not world readable as it may contain secrets + chown root:aptly-api /etc/aptly.conf + chmod 640 /etc/aptly.conf + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/aptly-api.service b/debian/aptly-api.service new file mode 100644 index 00000000..16d6383c --- /dev/null +++ b/debian/aptly-api.service @@ -0,0 +1,16 @@ +[Unit] +Description=Aptly REST API +After=network.target +Documentation=man:aptly(1) + +[Service] +User=aptly-api +Group=aptly-api +Environment=TERM=dumb +WorkingDirectory=~ +EnvironmentFile=/etc/default/aptly-api +ExecStart=/usr/bin/aptly api serve -config=/etc/aptly.conf ${APTLY_OPTIONS} -listen=${LISTEN_ADDRESS} +LimitNOFILE=32768 + +[Install] +WantedBy=multi-user.target diff --git a/debian/aptly.bash-completion b/debian/aptly.bash-completion new file mode 100644 index 00000000..af08cb0b --- /dev/null +++ b/debian/aptly.bash-completion @@ -0,0 +1 @@ +completion.d/aptly diff --git a/debian/aptly.conf b/debian/aptly.conf new file mode 100644 index 00000000..38a8a56a --- /dev/null +++ b/debian/aptly.conf @@ -0,0 +1,343 @@ +# Aptly Configuration File +########################### +# vim: : filetype=yaml + +# aptly 1.6.0 supports yaml configuraiton files with inline documentation and examples. +# Legacy json config files are still supported, and may be converted to yaml with `aptly config show -yaml` + +# Root directory for: +# - downloaded packages (`rootDir`/pool) +# - database (`rootDir`/db) +# - published repositories (`rootDir`/public) +root_dir: ~/.aptly + +# Log Level +# * debug +# * info +# * warning +# * error +log_level: info + +# Log Format +# * default (text) +# * json +log_format: default + +# Number of attempts to open database if it's locked by other instance +# * -1 (no retry) +database_open_attempts: -1 + +# Default Architectures +# empty list defaults to all available architectures +architectures: +# - amd64 + +# 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; +# if option is enabled, aptly stops checking for legacy paths; +# by default option is enabled for new aptly installations and disabled when +# upgrading from older versions +skip_legacy_pool: true + + +# Dependency following +####################### + +# Follow contents of `Suggests:` field when processing dependencies for the package +dep_follow_suggests: false + +# Follow contents of `Recommends:` field when processing dependencies for the package +dep_follow_recommends: false + +# When dependency looks like `package-a | package-b`, follow both variants always +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_verbose_resolve: false + + +# PPA +###### + +# Specify paramaters for short PPA url expansion +# empty defaults to output of `lsb_release` command +ppa_distributor_id: ubuntu + +# Codename for short PPA url expansion +ppa_codename: "" + + +# Aptly Server +############### + +# Serve published repos as well as API +serve_in_api_mode: false + +# Enable metrics for Prometheus client +enable_metrics_endpoint: false + +# Enable API documentation on /docs +enable_swagger_endpoint: false + +# OBSOLETE: use via url param ?_async=true +async_api: false + + +# Database +########### + +# Database backend +# Type must be one of: +# * leveldb (default) +# * etcd +database_backend: + type: leveldb + # Path to leveldb files + # empty dbPath defaults to `rootDir`/db + db_path: "" + + # type: etcd + # # URL to db server + # url: "127.0.0.1:2379" + + +# Mirroring +############ + +# Downloader +# * "default" +# * "grab" (more robust) +downloader: default + +# Number of parallel download threads to use when downloading packages +download_concurrency: 4 + +# Limit in kbytes/sec on download speed while mirroring remote repositories +download_limit: 0 + +# Number of retries for download attempts +download_retries: 0 + +# Download source packages per default +download_sourcepackages: false + + +# Signing +########## + +# GPG Provider +# * "internal" (Go internal implementation) +# * "gpg" (External `gpg` utility) +gpg_provider: gpg + +# Disable signing of published repositories +gpg_disable_sign: false + +# Disable signature verification of remote repositories +gpg_disable_verify: false + + +# Publishing +############# + +# Do not publish Contents files +skip_contents_publishing: false + +# Do not create bz2 files +skip_bz2_publishing: false + + +# Storage +########## + +# Filesystem publishing endpoints +# +# aptly defaults to publish to a single publish directory under `rootDir`/public. For +# a more advanced publishing strategy, you can define one or more filesystem endpoints in the +# `FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name +# and the following associated settings. +# +# In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name` +# with `endpoint-name` as the name given in the aptly configuration file. For example: +# +# `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily` +# +filesystem_publish_endpoints: + # # Endpoint Name + # test1: + # # Directory for publishing + # root_dir: /opt/srv/aptly_public + # # File Link Method for linking files from the internal pool to the published directory + # # * hardlink + # # * symlink + # # * copy + # link_method: hardlink + # # File Copare Method for comparing existing links from the internal pool to the published directory + # # Only used when "linkMethod" is set to "copy" + # # * md5 (default: compare md5 sum) + # # * 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:` +# +s3_publish_endpoints: + # # Endpoint Name + # test: + # # Amazon region for S3 bucket + # region: us-east-1 + # # Bucket name + # bucket: test-bucket + # # Prefix (optional) + # # publishing under specified prefix in the bucket, defaults to + # # no prefix (bucket root) + # prefix: "" + # # Default ACLs (optional) + # # assign ACL to published files: + # # * private (default, for use with apt S3 transport) + # # * public-read (public repository) + # # * none (don't set ACL) + # acl: private + # # Credentials (optional) + # # Amazon credentials to access S3 bucket. If not supplied, environment variables + # # `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` are used + # access_key_id: "" + # secret_access_key: "" + # session_token: "" + # # Endpoint (optional) + # # When using S3-compatible cloud storage, specify hostname of service endpoint here, + # # region is ignored if endpoint is set (set region to some human-readable name) + # # (should be left blank for real Amazon S3) + # endpoint: "" + # # Storage Class (optional) + # # Amazon S3 storage class, defaults to `STANDARD`. Other values + # # available: `REDUCED_REDUNDANCY` (lower price, lower redundancy) + # storage_class: STANDARD + # # Encryption Method (optional) + # # Server-side encryption method, defaults to none. Currently + # # the only available encryption method is `AES256` + # encryption_method: none + # # Plus Workaround (optional) + # # Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by + # # creating two copies of package files with `+` in filename: one original + # # and another one with spaces instead of plus signs + # # With `plusWorkaround` enabled, package files with plus sign + # # would be stored twice. aptly might not cleanup files with spaces when published + # # repository is dropped or updated (switched) to new version of repository (snapshot) + # plus_workaround: false + # # Disable MultiDel (optional) + # # For S3-compatible cloud storages which do not support `MultiDel` S3 API, + # # enable this setting (file deletion would be slower with this setting enabled) + # disable_multidel: false + # # Force Signature v2 (optional) + # # Disable Signature V4 support, useful with non-AWS S3-compatible object stores + # # which do not support SigV4, shouldn't be enabled for AWS + # force_sigv2: false + # # Force VirtualHosted Style (optional) + # # Disable path style visit, useful with non-AWS S3-compatible object stores + # # which only support virtual hosted style + # force_virtualhosted_style: false + # # Debug (optional) + # # Enables detailed request/response dump for each S3 operation + # debug: false + +# Swift Endpoint Support +# +# aptly can publish a repository directly to OpenStack Swift. +# Each endpoint has name and associated settings. +# +# In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before +# publishing prefix on the command line, e.g.: +# +# `aptly publish snapshot jessie-main swift:test:` +# +swift_publish_endpoints: + # # Endpoint Name + # test: + # # Container Name + # container: taylor1 + # # Prefix (optional) + # # Publish under specified prefix in the container, defaults to no prefix (container root) + # prefix: "" + # # Credentials (optional) + # # OpenStack credentials to access Keystone. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used + # username: "" + # password: "" + # # Domain (optional) + # # OpenStack domain + # domain: "" + # domain_id: "" + # # Tenant (optional) + # # OpenStack tenant (in order to use v2 authentication) + # tenant: "" + # tenant_id: "" + # tenant_domain: "" + # tenant_domain_id: "" + # # Auth URL (optional) + # # Full url of Keystone server (including port, and version). + # # Example `http://identity.example.com:5000/v2.0` + # auth_url: "" + +# Azure Endpoint Support +# +# aptly can be configured to publish repositories directly to Microsoft Azure Blob +# Storage. First, publishing endpoints should be described in the aptly +# configuration file. Each endpoint has its name and associated settings. +azure_publish_endpoints: + # # Endpoint Name + # test: + # # Container Name + # container: container1 + # # Prefix (optional) + # # Publishing under specified prefix in the container, defaults to no prefix (container root) + # prefix: "" + # # Credentials + # # Azure storage account access key to access blob storage + # account_name: "" + # account_key: "" + # # Endpoint URL + # # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string + # # defaults to "https://.blob.core.windows.net" + # endpoint: "" + +# Package Pool +# +# Location for storing downloaded packages +# Type must be one of: +# * local +# * azure +packagepool_storage: + # Local Pool + type: local + # Local Pool Path + # empty path defaults to `rootDir`/pool + path: + + # # Azure Azure Blob Storage Pool + # type: azure + # # Container Name + # container: pool1 + # # Prefix (optional) + # # Publishing under specified prefix in the container, defaults to no prefix (container root) + # prefix: "" + # # Credentials + # # Azure storage account access key to access blob storage + # account_name: "" + # account_key: "" + # # Endpoint URL + # # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string + # # defaults to "https://.blob.core.windows.net" + # endpoint: "" + diff --git a/debian/aptly.install b/debian/aptly.install new file mode 100644 index 00000000..27a513c6 --- /dev/null +++ b/debian/aptly.install @@ -0,0 +1,2 @@ +usr/bin/aptly +completion.d/_aptly usr/share/zsh/vendor-completions diff --git a/debian/aptly.manpages b/debian/aptly.manpages new file mode 100644 index 00000000..c261d746 --- /dev/null +++ b/debian/aptly.manpages @@ -0,0 +1 @@ +man/aptly.1 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..c5bdffa9 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,29 @@ +aptly (1.6.0) stable; urgency=medium + + * support reading filters from file or stdin + * fix mirroring source packages + * support yaml config per default + * provide swagger API documentation + * provide API for querying aptly storage usage + * provide snapshot pull via API + * support creating repos from snapshots + * fix mirroring flat remote repos + * support skeleton files for publishing + * use new azure sdk + * support updating the components of a published repo + * support publishing multiple distributions (-multi-dist) + * support etcd database + * allow slash (/) in distribution names + * support for storing the "local" pool on Azure + * provide copy package API + * fix publish concurrency and improve performance + * improved mirroring + * fix download throttling + * fix resuming package downloads + * fix ignoring signatures + * fix packages dependency resolution (Virtual Packages, version numbers in Provides) + * improved S3 support and performance + * fix race condition with goleveldb + * use go 1.22 + + -- André Roth Tue, 24 Dec 2024 17:44:35 +0100 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..b6c4d8de --- /dev/null +++ b/debian/control @@ -0,0 +1,133 @@ +Source: aptly +Section: utils +Priority: optional +Maintainer: André Roth +Build-Depends: bash-completion, + debhelper-compat (= 13), + dh-golang, + golang-go, + golang-github-aleksi-pointer-dev, + golang-github-awalterschulze-gographviz-dev, + golang-github-aws-aws-sdk-go-v2-dev (>= 1.24.1), + golang-github-aws-smithy-go-dev, + golang-github-azure-azure-pipeline-go-dev, + golang-github-azure-azure-storage-blob-go-dev, + golang-github-beorn7-perks-dev, + golang-github-cavaliergopher-grab-dev, + golang-github-cespare-xxhash-dev, + golang-github-cheggaaa-pb.v3-dev, + golang-github-cloudflare-circl-dev, + golang-github-coreos-go-semver-dev, + golang-github-coreos-go-systemd-dev, + golang-github-disposaboy-jsonconfigreader-dev, + golang-github-gin-contrib-sse-dev, + golang-github-gin-gonic-gin-dev, + golang-github-gogo-protobuf-dev, + golang-snappy-go-dev, + golang-github-google-uuid-dev, + golang-github-go-playground-locales-dev, + golang-github-go-playground-universal-translator-dev, + golang-github-go-playground-validator-v10-dev, + golang-gopkg-h2non-filetype.v1-dev, + golang-github-hashicorp-errwrap-dev, + golang-github-hashicorp-go-multierror-dev, + golang-github-jlaffaye-ftp-dev, + golang-github-kjk-lzma-dev, + golang-github-klauspost-compress-dev, + golang-github-klauspost-pgzip-dev, + golang-github-leodido-go-urn-dev, + golang-github-mattn-go-colorable-dev, + golang-github-mattn-go-ieproxy-dev, + golang-github-mattn-go-isatty-dev, + golang-github-mattn-go-runewidth-dev, + golang-github-mattn-go-shellwords-dev, + golang-github-mkrautz-goar-dev, + golang-github-munnerz-goautoneg-dev, + golang-github-mxk-go-flowrate-dev, + golang-github-ncw-swift-dev, + golang-github-pborman-uuid-dev, + golang-github-pelletier-go-toml, + golang-github-pkg-errors-dev, + golang-github-prometheus-client-golang-dev, + golang-github-prometheus-client-model-dev, + golang-github-prometheus-common-dev, + golang-github-prometheus-procfs-dev, + golang-github-protonmail-go-crypto-dev, + golang-github-rivo-uniseg-dev, + golang-github-rs-zerolog-dev (>= 1.29.1), + golang-github-saracen-walker-dev, + golang-github-smira-commander-dev, + golang-github-smira-flag-dev, + golang-github-smira-go-ftp-protocol-dev, + golang-github-smira-go-xz-dev, + golang-github-syndtr-goleveldb-dev, + golang-github-ugorji-go-codec, + golang-github-wsxiaoys-terminal-dev, + golang-golang-x-crypto-dev, + golang-golang-x-net-dev, + golang-golang-x-sync-dev, + golang-golang-x-sys-dev, + golang-golang-x-term-dev, + golang-golang-x-text-dev, + golang-golang-x-time-dev, + golang-google-genproto-dev, + golang-google-grpc-dev, + golang-google-protobuf-dev, + golang-gopkg-yaml.v2-dev, + golang-go.uber-multierr-dev, + golang-go.uber-zap-dev, + golang-etcd-server-dev (>= 3.5.15-7), + golang-gopkg-yaml.v3-dev, + git +Standards-Version: 4.7.0 +Homepage: https://www.aptly.info +Vcs-Git: https://github.com/aptly-dev/aptly.git +Vcs-Browser: https://github.com/aptly-dev/aptly +XS-Go-Import-Path: github.com/aptly-dev/aptly +Testsuite: autopkgtest-pkg-go + +Package: aptly +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, bzip2, xz-utils, gpgv, gpg +Suggests: graphviz +Conflicts: gnupg1, gpgv1 +Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using} +Description: Swiss army knife for Debian repository management - main package + It offers several features making it easy to manage Debian package + repositories: + . + - make mirrors of remote Debian/Ubuntu repositories, limiting by + components/architectures + - take snapshots of mirrors at any point in time, fixing state of + repository at some moment of time + - publish snapshot as Debian repository, ready to be consumed by apt + - controlled update of one or more packages in snapshot from upstream + mirror, tracking dependencies + - merge two or more snapshots into one + . + This is the main package, it contains the aptly command-line utility. + +Package: aptly-api +Architecture: any +Depends: ${misc:Depends}, aptly +Description: Swiss army knife for Debian repository management - API + It offers several features making it easy to manage Debian package + repositories: + . + - make mirrors of remote Debian/Ubuntu repositories, limiting by + components/architectures + - take snapshots of mirrors at any point in time, fixing state of + repository at some moment of time + - publish snapshot as Debian repository, ready to be consumed by apt + - controlled update of one or more packages in snapshot from upstream + mirror, tracking dependencies + - merge two or more snapshots into one + . + This package contains the aptly-api service. + +Package: aptly-dbg +Architecture: any +Depends: ${misc:Depends} +Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using} +Description: Debian repository management tool (debug files) + Debug symbols for aptly diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..cb7fed43 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,53 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: aptly +Source: http://www.aptly.info + +Files: * +Copyright: 2014 Andrey Smirnov , 2023 André Roth +License: Expat +Comment: Some of aptly's vendored dependencies are already in Debian, and + Alexandre Viau is well on his way to packaging the rest of them so that + aptly can properly depend on the corresponding Debian packages + (see https://bugs.debian.org/902128). + +Files: debian/* +Copyright: 2014 Sebastien Delafond +License: GPL-2+ + +License: GPL-2+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + . + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 00000000..6bde59c3 --- /dev/null +++ b/debian/manpages @@ -0,0 +1 @@ +src/github.com/aptly-dev/aptly/man/aptly.1 diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..30cf3c27 --- /dev/null +++ b/debian/rules @@ -0,0 +1,26 @@ +#!/usr/bin/make -f + +include /usr/share/dpkg/pkg-info.mk + +%: + dh $@ --buildsystem=golang --with=golang,bash-completion + +override_dh_auto_clean: + rm -rf build/ + rm -rf obj-$(DEB_TARGET_GNU_TYPE)/ + dh_auto_clean + +override_dh_auto_test: + # run during autopkgtests + +override_dh_auto_install: + dh_auto_install -- --no-source + +override_dh_strip: + dh_strip --dbg-package=aptly-dbg + +override_dh_auto_build: + echo $(DEB_VERSION) > obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/VERSION + mkdir -p obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian + cp debian/aptly.conf obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian/ + dh_auto_build diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..af745b31 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (git) diff --git a/debian/tests/control b/debian/tests/control new file mode 100644 index 00000000..0ae1d291 --- /dev/null +++ b/debian/tests/control @@ -0,0 +1,4 @@ +# This file is an addition to the autodep8 tests. + +Test-Command: HOME=/tmp aptly repo create autopkgtest +Restrictions: allow-stderr diff --git a/debian/upstream/metadata b/debian/upstream/metadata new file mode 100644 index 00000000..7571c2d7 --- /dev/null +++ b/debian/upstream/metadata @@ -0,0 +1,5 @@ +--- +Bug-Database: https://github.com/aptly-dev/aptly/issues +Bug-Submit: https://github.com/aptly-dev/aptly/issues/new +Repository: https://github.com/aptly-dev/aptly.git +Repository-Browse: https://github.com/aptly-dev/aptly diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..2c6231d2 --- /dev/null +++ b/debian/watch @@ -0,0 +1,5 @@ +version=4 +opts=\ +repacksuffix=+ds1,\ +dversionmangle=s/\+ds\d*$// \ +https://github.com/aptly-dev/aptly/tags .*/v(\d[\d\.]*)\.tar\.gz debian uupdate diff --git a/docs/Database.md b/docs/Database.md new file mode 100644 index 00000000..b4eca8d0 --- /dev/null +++ b/docs/Database.md @@ -0,0 +1,5 @@ +# Maintenance Operations +
+Manage aptly’s internal metadata database and package pool. + +
diff --git a/docs/Files.md b/docs/Files.md new file mode 100644 index 00000000..616a64ac --- /dev/null +++ b/docs/Files.md @@ -0,0 +1,12 @@ +# Upload Package Files +
+ +In order to add debian package files to a local repository, files are first uploaded to a temporary directory. +Then the directory (or a specific file within) is added to a repository. After adding to a repositorty, the directory resp. files are removed bt default. + +All uploaded files are stored under `/upload/` directory. + +For concurrent uploads from CI/CD pipelines, make sure the tempdir is unique. + + +
diff --git a/docs/Mirrors.md b/docs/Mirrors.md new file mode 100644 index 00000000..4bacaec1 --- /dev/null +++ b/docs/Mirrors.md @@ -0,0 +1,8 @@ +# Manage Remote Repository Mirrors +
+Manage mirrors of remote Debian repositories (http, https or ftp). + +Flat debian repositories, mirroring source packages and debian installers is supported. + +
+ diff --git a/docs/Packages.md b/docs/Packages.md new file mode 100644 index 00000000..18385c0e --- /dev/null +++ b/docs/Packages.md @@ -0,0 +1,5 @@ +# Search Package Collection +
+Perform operations on the whole collection of packages in apty database. +
+ diff --git a/docs/Publish.md b/docs/Publish.md new file mode 100644 index 00000000..0077f4d9 --- /dev/null +++ b/docs/Publish.md @@ -0,0 +1,21 @@ +# Publish Repositories, Snapshots, Mirrors +
+ +Publish snapshot or local repo as Debian repository to be used as APT source on Debian based systems. + +The published repository is signed with the user's GnuPG key. + +Repositories can be published to local directories, Amazon S3 buckets, Azure or Swift Storage. + +#### GPG Keys + +GPG key is required to sign any published repository. The key pari should be generated before publishing. + +Publiс part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository. + +#### Parameters + +Publish APIs use following convention to identify published repositories: `/api/publish/:prefix/:distribution`. `:distribution` is distribution name, while `:prefix` is `[:]` (storage is optional, it defaults to empty string), if publishing prefix contains slashes `/`, they should be replaced with underscores (`_`) and underscores +should be replaced with double underscore (`__`). To specify root `:prefix`, use `:.`, as `.` is ambigious in URLs. + +
diff --git a/docs/Repos.md b/docs/Repos.md new file mode 100644 index 00000000..05960c03 --- /dev/null +++ b/docs/Repos.md @@ -0,0 +1,9 @@ +# Manage Local Repositories +
+A local repository is a collection of versionned packages (usually custom packages created internally). + +Packages can be added, removed, moved or copied between repos. + +Local repositories can be published (either directly or via snapshot) to be used a APT source on a debian based system. +
+ diff --git a/docs/Snapshots.md b/docs/Snapshots.md new file mode 100644 index 00000000..21d0e526 --- /dev/null +++ b/docs/Snapshots.md @@ -0,0 +1,8 @@ +# Manage Snapshots +
+ +Local Repositories and Mirrors can be snapshotted to get an immutable state. + +Snapshots cab be merged, filtered, verified for missing dependencies. Snapshots can be published to be used as APT source. + +
diff --git a/docs/Status.md b/docs/Status.md new file mode 100644 index 00000000..2aac9e18 --- /dev/null +++ b/docs/Status.md @@ -0,0 +1,5 @@ +# Status Information +
+Various status information. + +
diff --git a/docs/Tasks.md b/docs/Tasks.md new file mode 100644 index 00000000..03668d8e --- /dev/null +++ b/docs/Tasks.md @@ -0,0 +1,8 @@ +# Background Tasks +
+ +Several API operations allow to be run in background asynchronously in a task. In that case, a Task object with an ID and a State is returned, which can be queried for progress. + +Tasks should be deleted once they are no longer in progress, in order to not cause memory overflows. + +
diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..aaf87195 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,9 @@ +Aptly operations are also available via REST API served with `aptly api serve`. + +On Debian based systems, a package `aptly-api` is available, which will run aptly as systemd service as dedicated aptly-api user. + +Some configuration changes (S3 publishing endpoints, ...) will require restarting the aptly service in order to take effect. + +The REST API shouldn't be exposed to the Internet as there is no authentication/protection, consider using a HTTP proxy (e.g. nginx) to add https and authentication. + +#### Aptly REST API Documentation diff --git a/docs/docs.html b/docs/docs.html new file mode 100644 index 00000000..d057ee1d --- /dev/null +++ b/docs/docs.html @@ -0,0 +1,175 @@ + + + + + Swagger UI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/docs/index.go b/docs/index.go new file mode 100644 index 00000000..ca4c914a --- /dev/null +++ b/docs/index.go @@ -0,0 +1,10 @@ +package docs + +import ( + _ "embed" // embed html below + + _ "github.com/swaggo/swag" // make sure swag is in go.mod +) + +//go:embed docs.html +var DocsHTML []byte diff --git a/docs/swagger.conf.tpl b/docs/swagger.conf.tpl new file mode 100644 index 00000000..31b0dfbf --- /dev/null +++ b/docs/swagger.conf.tpl @@ -0,0 +1,28 @@ +package docs + +// @title Aptly API +// @description.markdown + +// @contact.name Aptly +// @contact.url http://github.com/aptly-dev/aptly + +// @Tag.name Repos +// @Tag.description.markdown +// @Tag.name Files +// @Tag.description.markdown +// @Tag.name Mirrors +// @Tag.description.markdown +// @Tag.name Snapshots +// @Tag.description.markdown +// @Tag.name Publish +// @Tag.description.markdown +// @Tag.name Packages +// @Tag.description.markdown +// @Tag.name Status +// @Tag.description.markdown +// @Tag.name Database +// @Tag.description.markdown +// @Tag.name Tasks +// @Tag.description.markdown + +// version will be appended here: diff --git a/files/mocks.go b/files/mocks.go index 572ece28..411e73a9 100644 --- a/files/mocks.go +++ b/files/mocks.go @@ -5,19 +5,19 @@ import ( "github.com/aptly-dev/aptly/utils" ) -type mockChecksumStorage struct { - store map[string]utils.ChecksumInfo +type MockChecksumStorage struct { + Store map[string]utils.ChecksumInfo } // NewMockChecksumStorage creates aptly.ChecksumStorage for tests func NewMockChecksumStorage() aptly.ChecksumStorage { - return &mockChecksumStorage{ - store: make(map[string]utils.ChecksumInfo), + return &MockChecksumStorage{ + Store: make(map[string]utils.ChecksumInfo), } } -func (st *mockChecksumStorage) Get(path string) (*utils.ChecksumInfo, error) { - c, ok := st.store[path] +func (st *MockChecksumStorage) Get(path string) (*utils.ChecksumInfo, error) { + c, ok := st.Store[path] if !ok { return nil, nil } @@ -25,12 +25,12 @@ func (st *mockChecksumStorage) Get(path string) (*utils.ChecksumInfo, error) { return &c, nil } -func (st *mockChecksumStorage) Update(path string, c *utils.ChecksumInfo) error { - st.store[path] = *c +func (st *MockChecksumStorage) Update(path string, c *utils.ChecksumInfo) error { + st.Store[path] = *c return nil } // Check interface var ( - _ aptly.ChecksumStorage = &mockChecksumStorage{} + _ aptly.ChecksumStorage = &MockChecksumStorage{} ) diff --git a/files/package_pool.go b/files/package_pool.go index 8b1e8b25..e82a7447 100644 --- a/files/package_pool.go +++ b/files/package_pool.go @@ -3,13 +3,14 @@ package files import ( "fmt" "io" - "io/ioutil" "os" "path/filepath" + "sort" "sync" "syscall" "github.com/pborman/uuid" + "github.com/saracen/walker" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/utils" @@ -31,8 +32,7 @@ var ( // NewPackagePool creates new instance of PackagePool which specified root func NewPackagePool(root string, supportLegacyPaths bool) *PackagePool { - rootPath := filepath.Join(root, "pool") - rootPath, err := filepath.Abs(rootPath) + rootPath, err := filepath.Abs(root) if err != nil { panic(err) } @@ -81,7 +81,7 @@ func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) pool.Lock() defer pool.Unlock() - dirs, err := ioutil.ReadDir(pool.rootPath) + dirs, err := os.ReadDir(pool.rootPath) if err != nil { if os.IsNotExist(err) { return nil, nil @@ -99,13 +99,13 @@ func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) } result := []string{} + resultLock := &sync.Mutex{} for _, dir := range dirs { - err = filepath.Walk(filepath.Join(pool.rootPath, dir.Name()), func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } + err = walker.Walk(filepath.Join(pool.rootPath, dir.Name()), func(path string, info os.FileInfo) error { if !info.IsDir() { + resultLock.Lock() + defer resultLock.Unlock() result = append(result, path[len(pool.rootPath)+1:]) } return nil @@ -119,6 +119,7 @@ func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) } } + sort.Strings(result) return result, nil } @@ -378,6 +379,15 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check return poolPath, err } +func (pool *PackagePool) Size(path string) (size int64, err error) { + stat, err := pool.Stat(path) + if err != nil { + return 0, err + } + + return stat.Size(), nil +} + // Open returns io.ReadCloser to access the file func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) { return os.Open(filepath.Join(pool.rootPath, path)) diff --git a/files/package_pool_test.go b/files/package_pool_test.go index 3f65ed2c..4cd9476b 100644 --- a/files/package_pool_test.go +++ b/files/package_pool_test.go @@ -111,7 +111,7 @@ func (s *PackagePoolSuite) TestImportOk(c *C) { // SHA256 should be automatically calculated c.Check(s.checksum.SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") // checksum storage is filled with new checksum - c.Check(s.cs.(*mockChecksumStorage).store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") + c.Check(s.cs.(*MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") info, err := s.pool.Stat(path) c.Assert(err, IsNil) @@ -120,7 +120,7 @@ func (s *PackagePoolSuite) TestImportOk(c *C) { if isSameDevice(s) { c.Check(info.Sys().(*syscall.Stat_t).Nlink > 1, Equals, true) } else { - c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint64(1)) + c.Check(info.Sys().(*syscall.Stat_t).Nlink == 1, Equals, true) } // import as different name @@ -128,7 +128,7 @@ func (s *PackagePoolSuite) TestImportOk(c *C) { c.Check(err, IsNil) c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_some.deb") // checksum storage is filled with new checksum - c.Check(s.cs.(*mockChecksumStorage).store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") + c.Check(s.cs.(*MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12") // double import, should be ok s.checksum.SHA512 = "" // clear checksum @@ -139,7 +139,7 @@ func (s *PackagePoolSuite) TestImportOk(c *C) { c.Check(s.checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c") // clear checksum storage, and do double-import - delete(s.cs.(*mockChecksumStorage).store, path) + delete(s.cs.(*MockChecksumStorage).Store, path) s.checksum.SHA512 = "" // clear checksum path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false, s.cs) c.Check(err, IsNil) @@ -244,7 +244,7 @@ func (s *PackagePoolSuite) TestVerify(c *C) { c.Check(exists, Equals, false) // check existence, with missing checksum and no info in checksum storage - delete(s.cs.(*mockChecksumStorage).store, path) + delete(s.cs.(*MockChecksumStorage).Store, path) s.checksum.SHA512 = "" ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &s.checksum, s.cs) c.Check(ppath, Equals, path) @@ -306,6 +306,18 @@ func (s *PackagePoolSuite) TestImportOverwrite(c *C) { c.Check(err, ErrorMatches, "unable to import into pool.*") } +func (s *PackagePoolSuite) TestSize(c *C) { + path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false, s.cs) + c.Check(err, IsNil) + + size, err := s.pool.Size(path) + c.Assert(err, IsNil) + c.Check(size, Equals, int64(2738)) + + _, err = s.pool.Size("do/es/ntexist") + c.Assert(os.IsNotExist(err), Equals, true) +} + func (s *PackagePoolSuite) TestStat(c *C) { path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false, s.cs) c.Check(err, IsNil) @@ -347,7 +359,7 @@ func (s *PackagePoolSuite) TestLink(c *C) { if isSameDevice(s) { c.Check(info.Sys().(*syscall.Stat_t).Nlink > 2, Equals, true) } else { - c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint64(2)) + c.Check(info.Sys().(*syscall.Stat_t).Nlink == 2, Equals, true) } } @@ -365,7 +377,7 @@ func (s *PackagePoolSuite) TestSymlink(c *C) { if isSameDevice(s) { c.Check(info.Sys().(*syscall.Stat_t).Nlink > 2, Equals, true) } else { - c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint64(1)) + c.Check(info.Sys().(*syscall.Stat_t).Nlink == 1, Equals, true) } info, err = os.Lstat(dstPath) diff --git a/files/public.go b/files/public.go index fc87b410..848f0c23 100644 --- a/files/public.go +++ b/files/public.go @@ -5,11 +5,14 @@ import ( "io" "os" "path/filepath" + "sort" "strings" + "sync" "syscall" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/utils" + "github.com/saracen/walker" ) // PublishedStorage abstract file system with public dirs (published repos) @@ -118,37 +121,49 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress // LinkFromPool links package file from pool to dist's pool location // -// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) +// publishedPrefix is desired prefix for the location in the pool. +// publishedRelPath is desired location in pool (like pool/component/liba/libav/) // sourcePool is instance of aptly.PackagePool // sourcePath is a relative path to package file in package pool // // LinkFromPool returns relative path for the published file to be included in package index -func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool, +func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error { baseName := filepath.Base(fileName) - poolPath := filepath.Join(storage.rootPath, publishedDirectory, filepath.Dir(fileName)) + poolPath := filepath.Join(storage.rootPath, publishedPrefix, publishedRelPath, filepath.Dir(fileName)) + + var localSourcePool aptly.LocalPackagePool + if storage.linkMethod != LinkMethodCopy { + pp, ok := sourcePool.(aptly.LocalPackagePool) + if !ok { + return fmt.Errorf("cannot link %s from non-local pool %s", baseName, sourcePool) + } + + localSourcePool = pp + } err := os.MkdirAll(poolPath, 0777) if err != nil { return err } - var dstStat, srcStat os.FileInfo + var dstStat os.FileInfo dstStat, err = os.Stat(filepath.Join(poolPath, baseName)) if err == nil { // already exists, check source file - srcStat, err = sourcePool.Stat(sourcePath) - if err != nil { - // source file doesn't exist? problem! - return err - } if storage.linkMethod == LinkMethodCopy { + srcSize, err := sourcePool.Size(sourcePath) + if err != nil { + // source file doesn't exist? problem! + return err + } + if storage.verifyMethod == VerificationMethodFileSize { // if source and destination have the same size, no need to copy - if srcStat.Size() == dstStat.Size() { + if srcSize == dstStat.Size() { return nil } } else { @@ -165,6 +180,12 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin } } } else { + srcStat, err := localSourcePool.Stat(sourcePath) + if err != nil { + // source file doesn't exist? problem! + return err + } + srcSys := srcStat.Sys().(*syscall.Stat_t) dstSys := dstStat.Sys().(*syscall.Stat_t) @@ -219,9 +240,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin err = dst.Close() } else if storage.linkMethod == LinkMethodSymLink { - err = sourcePool.(aptly.LocalPackagePool).Symlink(sourcePath, filepath.Join(poolPath, baseName)) + err = localSourcePool.Symlink(sourcePath, filepath.Join(poolPath, baseName)) } else { - err = sourcePool.(aptly.LocalPackagePool).Link(sourcePath, filepath.Join(poolPath, baseName)) + err = localSourcePool.Link(sourcePath, filepath.Join(poolPath, baseName)) } return err @@ -231,12 +252,12 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { root := filepath.Join(storage.rootPath, prefix) result := []string{} + resultLock := &sync.Mutex{} - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } + err := walker.Walk(root, func(path string, info os.FileInfo) error { if !info.IsDir() { + resultLock.Lock() + defer resultLock.Unlock() result = append(result, path[len(root)+1:]) } return nil @@ -247,6 +268,7 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { return []string{}, nil } + sort.Strings(result) return result, err } diff --git a/files/public_test.go b/files/public_test.go index 16f724cb..f135bd3e 100644 --- a/files/public_test.go +++ b/files/public_test.go @@ -233,7 +233,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Assert(err, IsNil) // Test using hardlinks - err = s.storage.LinkFromPool(filepath.Join(t.prefix, t.publishedDirectory), t.sourcePath, pool, srcPoolPath, sourceChecksum, false) + err = s.storage.LinkFromPool(t.prefix, t.publishedDirectory, t.sourcePath, pool, srcPoolPath, sourceChecksum, false) c.Assert(err, IsNil) st, err := os.Stat(filepath.Join(s.storage.rootPath, t.prefix, t.expectedFilename)) @@ -243,7 +243,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(int(info.Nlink), Equals, 3) // Test using symlinks - err = s.storageSymlink.LinkFromPool(filepath.Join(t.prefix, t.publishedDirectory), t.sourcePath, pool, srcPoolPath, sourceChecksum, false) + err = s.storageSymlink.LinkFromPool(t.prefix, t.publishedDirectory, t.sourcePath, pool, srcPoolPath, sourceChecksum, false) c.Assert(err, IsNil) st, err = os.Lstat(filepath.Join(s.storageSymlink.rootPath, t.prefix, t.expectedFilename)) @@ -254,7 +254,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(int(info.Mode&syscall.S_IFMT), Equals, int(syscall.S_IFLNK)) // Test using copy with checksum verification - err = s.storageCopy.LinkFromPool(filepath.Join(t.prefix, t.publishedDirectory), t.sourcePath, pool, srcPoolPath, sourceChecksum, false) + err = s.storageCopy.LinkFromPool(t.prefix, t.publishedDirectory, t.sourcePath, pool, srcPoolPath, sourceChecksum, false) c.Assert(err, IsNil) st, err = os.Stat(filepath.Join(s.storageCopy.rootPath, t.prefix, t.expectedFilename)) @@ -264,7 +264,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(int(info.Nlink), Equals, 1) // Test using copy with size verification - err = s.storageCopySize.LinkFromPool(filepath.Join(t.prefix, t.publishedDirectory), t.sourcePath, pool, srcPoolPath, sourceChecksum, false) + err = s.storageCopySize.LinkFromPool(t.prefix, t.publishedDirectory, t.sourcePath, pool, srcPoolPath, sourceChecksum, false) c.Assert(err, IsNil) st, err = os.Stat(filepath.Join(s.storageCopySize.rootPath, t.prefix, t.expectedFilename)) @@ -289,7 +289,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Assert(err, IsNil) nlinks := int(st.Sys().(*syscall.Stat_t).Nlink) - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) c.Check(err, ErrorMatches, ".*file already exists and is different") st, err = pool.Stat(srcPoolPath) @@ -297,7 +297,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(int(st.Sys().(*syscall.Stat_t).Nlink), Equals, nlinks) // linking with force - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, true) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, true) c.Check(err, IsNil) st, err = pool.Stat(srcPoolPath) @@ -305,21 +305,21 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(int(st.Sys().(*syscall.Stat_t).Nlink), Equals, nlinks+1) // Test using symlinks - err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) + err = s.storageSymlink.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) c.Check(err, ErrorMatches, ".*file already exists and is different") - err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, true) + err = s.storageSymlink.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, true) c.Check(err, IsNil) // Test using copy with checksum verification - err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) + err = s.storageCopy.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) c.Check(err, ErrorMatches, ".*file already exists and is different") - err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, true) + err = s.storageCopy.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, true) c.Check(err, IsNil) // Test using copy with size verification (this will NOT detect the difference) - err = s.storageCopySize.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) + err = s.storageCopySize.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, srcPoolPath, sourceChecksum, false) c.Check(err, IsNil) } diff --git a/go.mod b/go.mod index 4c2e85ad..b2bb72bc 100644 --- a/go.mod +++ b/go.mod @@ -1,48 +1,132 @@ module github.com/aptly-dev/aptly -go 1.15 +go 1.22.7 require ( - github.com/AlekSi/pointer v1.0.0 - github.com/Azure/azure-storage-blob-go v0.15.0 - github.com/DisposaBoy/JsonConfigReader v0.0.0-20130112093355-33a99fdf1d5e - github.com/awalterschulze/gographviz v0.0.0-20160912181450-761fd5fbb34e - github.com/aws/aws-sdk-go v1.25.0 + github.com/AlekSi/pointer v1.1.0 + github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 + github.com/awalterschulze/gographviz v2.0.1+incompatible github.com/cavaliergopher/grab/v3 v3.0.1 - github.com/cheggaaa/pb v1.0.10 - github.com/fatih/color v1.7.0 // indirect - github.com/gin-gonic/gin v1.7.7 - github.com/go-playground/validator/v10 v10.10.1 // indirect - github.com/h2non/filetype v1.0.5 - github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc // indirect - github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d - github.com/klauspost/compress v1.13.6 + github.com/cheggaaa/pb v1.0.25 + github.com/gin-gonic/gin v1.9.0 + github.com/go-playground/validator/v10 v10.11.2 // indirect + 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.17.9 github.com/klauspost/pgzip v1.2.5 - github.com/mattn/go-colorable v0.1.2 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-runewidth v0.0.2 // indirect - github.com/mattn/go-shellwords v1.0.2 + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-shellwords v1.0.12 github.com/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f - github.com/ncw/swift v1.0.30 - github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3 + github.com/ncw/swift v1.0.53 + github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.12.1 - github.com/smartystreets/gunit v1.0.4 // indirect + github.com/prometheus/client_golang v1.20.0 + github.com/rs/zerolog v1.29.1 + github.com/saracen/walker v0.1.2 github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5 github.com/smira/flag v0.0.0-20170926215700-695ea5e84e76 - github.com/smira/go-aws-auth v0.0.0-20180731211914-8b73995fd8d1 github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d - github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014 - github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d - github.com/ugorji/go/codec v1.2.7 + github.com/smira/go-xz v0.1.0 + github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca + github.com/ugorji/go/codec v1.2.11 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 - golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 - golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 - golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 - google.golang.org/protobuf v1.28.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sys v0.28.0 + golang.org/x/term v0.27.0 + golang.org/x/time v0.5.0 + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c - gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect - gopkg.in/h2non/filetype.v1 v1.0.1 // indirect +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.8.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudflare/circl v1.4.0 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + go.etcd.io/etcd/api/v3 v3.5.15 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.1 // indirect + gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 + github.com/ProtonMail/go-crypto v1.0.0 + github.com/aws/aws-sdk-go-v2 v1.32.5 + github.com/aws/aws-sdk-go-v2/config v1.28.5 + github.com/aws/aws-sdk-go-v2/credentials v1.17.46 + github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 + github.com/aws/smithy-go v1.22.1 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.3 + go.etcd.io/etcd/client/v3 v3.5.15 + gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 370f953d..22430504 100644 --- a/go.sum +++ b/go.sum @@ -1,641 +1,429 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE= -github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8= -github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= -github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DisposaBoy/JsonConfigReader v0.0.0-20130112093355-33a99fdf1d5e h1:rv5qJCfIzQhhefHp8MO98hoGRI3mdps2iiGA3o4nm8A= -github.com/DisposaBoy/JsonConfigReader v0.0.0-20130112093355-33a99fdf1d5e/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/awalterschulze/gographviz v0.0.0-20160912181450-761fd5fbb34e h1:24jix3mqu421BBMWbVBOl5pDw0f9ncazW10kaMywzHQ= -github.com/awalterschulze/gographviz v0.0.0-20160912181450-761fd5fbb34e/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= -github.com/aws/aws-sdk-go v1.25.0 h1:MyXUdCesJLBvSSKYcaKeeEwxNUwUpG6/uqVYeH/Zzfo= -github.com/aws/aws-sdk-go v1.25.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= +github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +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/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/awalterschulze/gographviz v2.0.1+incompatible h1:XIECBRq9VPEQqkQL5pw2OtjCAdrtIgFKoJU8eT98AS8= +github.com/awalterschulze/gographviz v2.0.1+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= +github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= +github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= +github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= +github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb v1.0.10 h1:CNg2511WECXZ7Ja6jjyz9CMBpQOrMuP5+H5zfjgVi/Q= -github.com/cheggaaa/pb v1.0.10/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.25 h1:tFpebHTkI7QZx1q1rWGOKhbunhZ3fMaxTvHDWn1bH/4= +github.com/cheggaaa/pb v1.0.25/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= +github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +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= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= -github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= -github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/h2non/filetype v1.0.5 h1:Esu2EFM5vrzNynnGQpj0nxhCkzVQh2HRY7AXUh/dyJM= -github.com/h2non/filetype v1.0.5/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= +github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc h1:lWFup/SOhwcpvRJIFqx/WQis5U4SrOSyWfSqvfdF09w= -github.com/jlaffaye/ftp v0.0.0-20180404123514-2403248fa8cc/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= +github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM= -github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-shellwords v1.0.2 h1:5FJ7APbaUYdUTxxP/XXltfy/mICrGqugUEClfnj+D3Y= -github.com/mattn/go-shellwords v1.0.2/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da h1:Iu5QFXIMK/YrHJ0NgUnK0rqYTTyb0ldt/rqNenAj39U= github.com/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da/go.mod h1:NfnmoBY0gGkr3/NmI+DP/UXbZvOCurCUYAzOdYJjlOc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ncw/swift v1.0.30 h1:CrRYmUc+mFGIvBiS5JIA4sIdURfDpJ4CGmpmR9mQAZ0= -github.com/ncw/swift v1.0.30/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= +github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3 h1:9J0mOv1rXIBlRjQCiAGyx9C3dZZh5uIa3HU0oTV8v1E= -github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= -github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= -github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= -github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= +github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo= +github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE= github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5 h1:jLFwP6SDEUHmb6QSu5n2FHseWzMio1ou1FV9p7W6p7I= github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5/go.mod h1:XTQy55hw5s3pxmC42m7X0/b+9naXQ1rGN9Of6BGIZmU= github.com/smira/flag v0.0.0-20170926215700-695ea5e84e76 h1:OM075OkN4x9IB1mbzkzaKaJjFxx8Mfss8Z3E1LHwawQ= github.com/smira/flag v0.0.0-20170926215700-695ea5e84e76/go.mod h1:KQ5bP0mZypI2qXa4Wjk+r7B6Wt/0L8/DvJwpbR1UWg4= -github.com/smira/go-aws-auth v0.0.0-20180731211914-8b73995fd8d1 h1:VPv+J50mFyP42/GzYhGuT4MJK8w/dlLt4jkoO5yhJRs= -github.com/smira/go-aws-auth v0.0.0-20180731211914-8b73995fd8d1/go.mod h1:KKhbssKjyR//TUP31t3ksE2b6oeAw328JzwmFJnzRCw= github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d h1:rvtR4+9N2LWPo0UHe6/aHvWpqD9Dhf10P2bfGFht74g= github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d/go.mod h1:Jm7yHrROA5tC42gyJ5EwiR8EWp0PUy0qOc4sE7Y8Uzo= -github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014 h1:tne8XW3soRDJn4DIiqBc4jw+DPashtFMTSC9G0pC3ug= -github.com/smira/go-xz v0.0.0-20150414201226-0c531f070014/go.mod h1:smSuTvETRIkX95VAIWBdKoGJuUxif7NT7FgbkpVqOiA= +github.com/smira/go-xz v0.1.0 h1:1zVLT1sITUKcWNysfHMLZWJ2Yh7yJfhREsgmUdK4zb0= +github.com/smira/go-xz v0.1.0/go.mod h1:OmdEWnIIkuLzRLHGF4YtjDzF9VFUevEcP6YxDPRqVrs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= -github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= +github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk= +go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM= +go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA= +go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= +go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= +go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +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= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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/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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 h1:D1v9ucDTYBtbz5vNuBbAhIMAGhQhJ6Ym5ah3maMVNX4= -golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 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= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/h2non/filetype.v1 v1.0.1 h1:JMZLYHwIsvWGh+6UcU//eA1aiM8g4SaZm3lJweIR5Ew= -gopkg.in/h2non/filetype.v1 v1.0.1/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/http/download.go b/http/download.go index 3c2a1ebd..be7b2c78 100644 --- a/http/download.go +++ b/http/download.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -47,29 +46,27 @@ func NewDownloader(downLimit int64, maxTries int, progress aptly.Progress) aptly transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{}) downloader := &downloaderImpl{ - progress: progress, - maxTries: maxTries, + progress: progress, + maxTries: maxTries, + aggWriter: io.Writer(progress), client: &http.Client{ Transport: &transport, }, } - progressWriter := io.Writer(progress) if progress == nil { - progressWriter = ioutil.Discard + downloader.aggWriter = io.Discard + } + if downLimit > 0 { + downloader.aggWriter = flowrate.NewWriter(downloader.aggWriter, downLimit) } downloader.client.CheckRedirect = downloader.checkRedirect - if downLimit > 0 { - downloader.aggWriter = flowrate.NewWriter(progressWriter, downLimit) - } else { - downloader.aggWriter = progressWriter - } return downloader } -func (downloader *downloaderImpl) checkRedirect(req *http.Request, via []*http.Request) error { +func (downloader *downloaderImpl) checkRedirect(req *http.Request, _ []*http.Request) error { if downloader.progress != nil { downloader.progress.Printf("Following redirect to %s...\n", req.URL) } @@ -136,6 +133,8 @@ func retryableError(err error) bool { } switch err { + case context.Canceled: + return false case io.EOF: return true case io.ErrUnexpectedEOF: @@ -177,7 +176,7 @@ func (downloader *downloaderImpl) DownloadWithChecksum(ctx context.Context, url expected *utils.ChecksumInfo, ignoreMismatch bool) error { if downloader.progress != nil { - downloader.progress.Printf("Downloading %s...\n", url) + downloader.progress.Printf("Downloading: %s\n", url) defer downloader.progress.Flush() } req, err := downloader.newRequest(ctx, "GET", url) @@ -193,7 +192,7 @@ func (downloader *downloaderImpl) DownloadWithChecksum(ctx context.Context, url if err != nil { if retryableError(err) { if downloader.progress != nil { - downloader.progress.Printf("Error downloading %s: %s retrying...\n", url, err) + downloader.progress.Printf("Error (retrying): %s\n", err) } maxTries-- time.Sleep(delay) @@ -204,15 +203,12 @@ func (downloader *downloaderImpl) DownloadWithChecksum(ctx context.Context, url } } else { if downloader.progress != nil { - downloader.progress.Printf("Error downloading %s: %s cannot retry...\n", url, err) + downloader.progress.Printf("Error: %s \n", err) } break } } else { // get out of the loop - if downloader.progress != nil { - downloader.progress.Printf("Success downloading %s\n", url) - } break } if downloader.progress != nil { @@ -223,7 +219,7 @@ func (downloader *downloaderImpl) DownloadWithChecksum(ctx context.Context, url // still an error after retrying, giving up if err != nil { if downloader.progress != nil { - downloader.progress.Printf("Giving up on %s...\n", url) + downloader.progress.Printf("Download Error: %s\n", url) } return err } @@ -264,11 +260,7 @@ func (downloader *downloaderImpl) download(req *http.Request, url, destination s defer outfile.Close() checksummer := utils.NewChecksumWriter() - writers := []io.Writer{outfile} - - if downloader.progress != nil { - writers = append(writers, downloader.progress) - } + writers := []io.Writer{outfile, downloader.aggWriter} if expected != nil { writers = append(writers, checksummer) diff --git a/http/download_test.go b/http/download_test.go index 7f89a777..d0feccf3 100644 --- a/http/download_test.go +++ b/http/download_test.go @@ -3,7 +3,6 @@ package http import ( "context" "fmt" - "io/ioutil" "net" "net/http" "os" @@ -27,7 +26,7 @@ type DownloaderSuiteBase struct { } func (s *DownloaderSuiteBase) SetUpTest(c *C) { - s.tempfile, _ = ioutil.TempFile(os.TempDir(), "aptly-test") + s.tempfile, _ = os.CreateTemp(os.TempDir(), "aptly-test") s.l, _ = net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) s.url = fmt.Sprintf("http://localhost:%d", s.l.Addr().(*net.TCPAddr).Port) @@ -43,7 +42,7 @@ func (s *DownloaderSuiteBase) SetUpTest(c *C) { close(s.ch) }() - s.progress = console.NewProgress() + s.progress = console.NewProgress(false) s.progress.Start() s.d = NewDownloader(0, 1, s.progress) @@ -116,7 +115,7 @@ func (s *DownloaderSuite) TestDownload404(c *C) { } func (s *DownloaderSuite) TestDownloadConnectError(c *C) { - c.Assert(s.d.Download(s.ctx, "http://nosuch.host/", s.tempfile.Name()), + c.Assert(s.d.Download(s.ctx, "http://nosuch.host.invalid./", s.tempfile.Name()), ErrorMatches, ".*no such host") } @@ -151,7 +150,17 @@ func (s *DownloaderSuite) TestGetLength404(c *C) { } func (s *DownloaderSuite) TestGetLengthConnectError(c *C) { - _, err := s.d.GetLength(s.ctx, "http://nosuch.host/") + _, err := s.d.GetLength(s.ctx, "http://nosuch.host.invalid./") c.Assert(err, ErrorMatches, ".*no such host") } + +func (s *DownloaderSuite) TestContextCancel(c *C) { + ctx, cancel := context.WithCancel(s.ctx) + s.ctx = ctx + + cancel() + _, err := s.d.GetLength(s.ctx, "http://nosuch.host.invalid./") + + c.Assert(err, ErrorMatches, ".*context canceled.*") +} diff --git a/http/fake.go b/http/fake.go index 2cbb72d6..40a1093d 100644 --- a/http/fake.go +++ b/http/fake.go @@ -61,7 +61,7 @@ func (f *FakeDownloader) Empty() bool { } // GetLength returns content length of given url -func (f *FakeDownloader) GetLength(ctx context.Context, url string) (int64, error) { +func (f *FakeDownloader) GetLength(_ context.Context, url string) (int64, error) { expectation, err := f.getExpectedRequest(url) if err != nil { return -1, err @@ -89,7 +89,7 @@ func (f *FakeDownloader) getExpectedRequest(url string) (*expectedRequest, error } // DownloadWithChecksum performs fake download by matching against first expectation in the queue or any expectation, with cheksum verification -func (f *FakeDownloader) DownloadWithChecksum(ctx context.Context, url string, filename string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { +func (f *FakeDownloader) DownloadWithChecksum(_ context.Context, url string, filename string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { expectation, err := f.getExpectedRequest(url) if err != nil { return err diff --git a/http/grab.go b/http/grab.go index 2d07521c..51fc672b 100644 --- a/http/grab.go +++ b/http/grab.go @@ -59,14 +59,13 @@ func (d *GrabDownloader) DownloadWithChecksum(ctx context.Context, url string, d // Success break } - d.log("Error downloading %s: %v\n", url, err) if retryableError(err) { maxTries-- - d.log("Retrying download %s: %d\n", url, maxTries) + d.log("Retrying %d %s\n", maxTries, url) time.Sleep(delay) } else { // Can't retry - d.log("Cannot retry download %s\n", url) + d.log("Error (retrying): HTTP code %s while fetching %s\n", err, url) break } } @@ -114,9 +113,8 @@ func (d *GrabDownloader) maybeSetupChecksum(req *grab.Request, expected *utils.C return nil } -func (d *GrabDownloader) download(ctx context.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { - // TODO clean up dest dir on permanent failure - d.log("Download %s -> %s\n", url, destination) +func (d *GrabDownloader) download(_ context.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { + d.log("Downloading: %s\n", url) req, err := grab.NewRequest(destination, url) if err != nil { @@ -155,7 +153,7 @@ func (d *GrabDownloader) GetProgress() aptly.Progress { return d.progress } -func (d *GrabDownloader) GetLength(ctx context.Context, url string) (int64, error) { +func (d *GrabDownloader) GetLength(_ context.Context, url string) (int64, error) { resp, err := http.Head(url) if err != nil { return -1, err diff --git a/http/grab_test.go b/http/grab_test.go index 3980c453..dacdfa8f 100644 --- a/http/grab_test.go +++ b/http/grab_test.go @@ -3,7 +3,6 @@ package http import ( "context" "fmt" - "io/ioutil" "net" "net/http" "os" @@ -26,7 +25,7 @@ type GrabDownloaderSuiteBase struct { } func (s *GrabDownloaderSuiteBase) SetUpTest(c *C) { - s.tempfile, _ = ioutil.TempFile(os.TempDir(), "aptly-test") + s.tempfile, _ = os.CreateTemp(os.TempDir(), "aptly-test") s.l, _ = net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) s.url = fmt.Sprintf("http://localhost:%d", s.l.Addr().(*net.TCPAddr).Port) @@ -42,7 +41,7 @@ func (s *GrabDownloaderSuiteBase) SetUpTest(c *C) { close(s.ch) }() - s.progress = console.NewProgress() + s.progress = console.NewProgress(false) s.progress.Start() s.d = NewGrabDownloader(0, 1, s.progress) @@ -107,7 +106,7 @@ func (s *GrabDownloaderSuite) TestDownload404(c *C) { } func (s *GrabDownloaderSuite) TestDownloadConnectError(c *C) { - c.Assert(s.d.Download(s.ctx, "http://nosuch.host/", s.tempfile.Name()), + c.Assert(s.d.Download(s.ctx, "http://nosuch.host.invalid./", s.tempfile.Name()), ErrorMatches, ".*no such host") } @@ -131,7 +130,7 @@ func (s *GrabDownloaderSuite) TestGetLength404(c *C) { } func (s *GrabDownloaderSuite) TestGetLengthConnectError(c *C) { - _, err := s.d.GetLength(s.ctx, "http://nosuch.host/") + _, err := s.d.GetLength(s.ctx, "http://nosuch.host.invalid./") c.Assert(err, ErrorMatches, ".*no such host") } diff --git a/http/temp.go b/http/temp.go index 8e6a3fde..ddc2d3af 100644 --- a/http/temp.go +++ b/http/temp.go @@ -2,7 +2,6 @@ package http import ( "context" - "io/ioutil" "os" "path/filepath" @@ -21,7 +20,7 @@ func DownloadTemp(ctx context.Context, downloader aptly.Downloader, url string) // // Temporary file would be already removed, so no need to cleanup func DownloadTempWithChecksum(ctx context.Context, downloader aptly.Downloader, url string, expected *utils.ChecksumInfo, ignoreMismatch bool) (*os.File, error) { - tempdir, err := ioutil.TempDir(os.TempDir(), "aptly") + tempdir, err := os.MkdirTemp(os.TempDir(), "aptly") if err != nil { return nil, err } diff --git a/main.go b/main.go index 80093443..6a5247cc 100644 --- a/main.go +++ b/main.go @@ -1,25 +1,28 @@ package main import ( - "math/rand" "os" - "time" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/cmd" + + _ "embed" ) -// Version variable, filled in at link time +//go:generate sh -c "make -s version | tr -d '\n' > VERSION" +//go:embed VERSION var Version string +//go:embed debian/aptly.conf +var AptlyConf []byte + func main() { if Version == "" { Version = "unknown" } aptly.Version = Version - - rand.Seed(time.Now().UnixNano()) + aptly.AptlyConf = AptlyConf os.Exit(cmd.Run(cmd.RootCommand(), os.Args[1:], true)) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000..750073b7 --- /dev/null +++ b/main_test.go @@ -0,0 +1,61 @@ +//go:build testruncli +// +build testruncli + +package main + +import ( + "flag" + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/aptly-dev/aptly/aptly" + "github.com/aptly-dev/aptly/cmd" +) + +func filterOutTestArgs(args []string) (out []string) { + for _, arg := range args { + if !strings.Contains(arg, "-test.coverprofile") { + out = append(out, arg) + } + } + return +} + +// redefine all the flags otherwise the go testing tool +// is not able to parse them ... +var _ = flag.Int("db-open-attempts", 10, "number of attempts to open DB if it's locked by other instance") +var _ = flag.Bool("dep-follow-suggests", false, "when processing dependencies, follow Suggests") +var _ = flag.Bool("dep-follow-source", false, "when processing dependencies, follow from binary to Source packages") +var _ = flag.Bool("dep-follow-recommends", false, "when processing dependencies, follow Recommends") +var _ = flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if dependency is 'a|b'") +var _ = flag.Bool("dep-verbose-resolve", false, "when processing dependencies, print detailed logs") +var _ = flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available") +var _ = flag.String("config", "", "location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf)") +var _ = flag.String("gpg-provider", "", "PGP implementation (\"gpg\", \"gpg1\", \"gpg2\" for external gpg or \"internal\" for Go internal implementation)") + +var _ = flag.String("cpuprofile", "", "write cpu profile to file") +var _ = flag.String("memprofile", "", "write memory profile to this file") +var _ = flag.String("memstats", "", "write memory stats periodically to this file") +var _ = flag.Duration("meminterval", 100*time.Millisecond, "memory stats dump interval") + +var _ = flag.Bool("raw", false, "raw") +var _ = flag.String("sort", "false", "sort") +var _ = flag.Bool("json", false, "json") + +func TestRunMain(t *testing.T) { + if Version == "" { + Version = "unknown" + } + + aptly.Version = Version + aptly.AptlyConf = AptlyConf + + args := filterOutTestArgs(os.Args[1:]) + root := cmd.RootCommand() + root.UsageLine = "aptly" + + fmt.Printf("EXIT: %d\n", cmd.Run(root, args, true)) +} diff --git a/man/Makefile b/man/Makefile index de73526d..064630cb 100644 --- a/man/Makefile +++ b/man/Makefile @@ -4,7 +4,7 @@ all: $(RUBYBINPATH)/ronn generate $(RUBYBINPATH)/ronn: gem install --user-install specific_install - gem specific_install --user-install -l smira/ronn + gem specific_install --user-install -l https://github.com/smira/ronn.git -b 49e19e2409e462a96fd0f2a50ceead07f392893d generate: PATH=$(RUBYBINPATH):$(PATH) go run ../_man/gen.go diff --git a/man/aptly.1 b/man/aptly.1 index 4cc042a4..bd6ad223 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "APTLY" "1" "January 2022" "" "" +.TH "APTLY" "1" "December 2024" "" "" . .SH "NAME" \fBaptly\fR \- Debian repository management tool @@ -25,284 +25,393 @@ aptly is a tool to create partial and full mirrors of remote repositories, manag aptly\(cqs goal is to establish repeatability and controlled changes in a package\-centric environment\. aptly allows one to fix a set of packages in a repository, so that package installation and upgrade becomes deterministic\. At the same time aptly allows one to perform controlled, fine\-grained changes in repository contents to transition your package environment to new version\. . .SH "CONFIGURATION" -aptly looks for configuration file first in \fB~/\.aptly\.conf\fR then in \fB/etc/aptly\.conf\fR and, if no config file found, new one is created in home directory\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly\fR, it will be created if missing\. +aptly looks for configuration file first in \fB~/\.aptly\.conf\fR then in \fB/usr/local/etc/aptly\.conf\fR and \fB/etc/aptly\.conf\fR\. If no config file found (or they are not readable), a new one is created in the home directory\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly/\fR, it will be created if missing\. . .P -Configuration file is stored in JSON format (default values shown below): +With aptly version 1\.6\.0, yaml configuration with inline documentation is supported and recommended (see \fBdebian/aptly\.conf\fR)\. +. +.P +The legacy json configuration is still supported (and also supports comments): . .IP "" 4 . .nf +// vim: : filetype=json +// json configuration file with comments +// validate with: sed \(cq/\e/\e//d\(cq aptly\.conf | json_pp { - "rootDir": "$HOME/\.aptly", - "downloadConcurrency": 4, - "downloadSpeedLimit": 0, - "downloadRetries": 0, - "downloader": "default", - "databaseOpenAttempts": 10, + +// Aptly Configuration File +//////////////////////////// + + // Root directory for: + // \- downloaded packages (`rootDir`/pool) + // \- database (`rootDir`/db) + // \- published repositories (`rootDir`/public) + "rootDir": "~/\.aptly", + + // Number of attempts to open database if it\(cqs locked by other instance + // * \-1 (no retry) + "databaseOpenAttempts": \-1, + + // Log Level + // * debug + // * info + // * warning + // * error + "logLevel": "info", + + // Log Format + // * default (text) + // * json + "logFormat": "default", + + // Default Architectures + // empty array defaults to all available architectures "architectures": [], + + // Follow contents of `Suggests:` field when processing dependencies for the package "dependencyFollowSuggests": false, + + // Follow contents of `Recommends:` field when processing dependencies for the package "dependencyFollowRecommends": false, + + // When dependency looks like `package\-a | package\-b`, follow both variants always "dependencyFollowAllVariants": false, + + // Follow dependency from binary package to source package "dependencyFollowSource": false, + + // Log additional details while resolving dependencies (useful for debugging) "dependencyVerboseResolve": false, - "gpgDisableSign": false, - "gpgDisableVerify": false, - "gpgProvider": "gpg", - "downloadSourcePackages": false, - "skipLegacyPool": true, + + // Specifies paramaters for short PPA url expansion + // empty defaults to output of `lsb_release` command "ppaDistributorID": "ubuntu", + + // Codename for short PPA url expansion "ppaCodename": "", + + // 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; + // if option is enabled, aptly stops checking for legacy paths; + // by default option is enabled for new aptly installations and disabled when + // upgrading from older versions + "skipLegacyPool": true, + + +// Aptly Server +//////////////// + + // Serve published repos as well as API + "serveInAPIMode": false, + + // Enable metrics for Prometheus client + "enableMetricsEndpoint": false, + + // Enable API documentation on /docs + "enableSwaggerEndpoint": false, + + // OBSOLETE: use via url param ?_async=true + "AsyncAPI": false, + + +// Database +//////////// + + // Database backend + // Type must be one of: + // * leveldb (default) + // * etcd + "databaseBackend": { + // LevelDB + "type": "leveldb", + // Path to leveldb files + // empty dbPath defaults to `rootDir`/db + "dbPath": "" + + // // etcd + // "type": "etcd", + // // URL to db server + // "url": "127\.0\.0\.1:2379" + }, + + +// Mirroring +///////////// + + // Downloader + // * "default" + // * "grab" (more robust) + "downloader": "default", + + // Number of parallel download threads to use when downloading packages + "downloadConcurrency": 4, + + // Limit in kbytes/sec on download speed while mirroring remote repositories + "downloadSpeedLimit": 0, + + // Number of retries for download attempts + "downloadRetries": 0, + + // Download source packages per default + "downloadSourcePackages": false, + + +// Signing +/////////// + + // GPG Provider + // * "internal" (Go internal implementation) + // * "gpg" (External `gpg` utility) + "gpgProvider": "gpg", + + // Disable signing of published repositories + "gpgDisableSign": false, + + // Disable signature verification of remote repositories + "gpgDisableVerify": false, + + +// Publishing +////////////// + + // Do not publish Contents files "skipContentsPublishing": false, + + // Do not create bz2 files + "skipBz2Publishing": false, + + +// Storage +/////////// + + // Filesystem publishing endpoints + // + // aptly defaults to publish to a single publish directory under `rootDir`/public\. For + // a more advanced publishing strategy, you can define one or more filesystem endpoints in the + // `FileSystemPublishEndpoints` list of the aptly configuration file\. Each endpoint has a name + // and the following associated settings\. + // + // In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint\-name` + // with `endpoint\-name` as the name given in the aptly configuration file\. For example: + // + // `aptly publish snapshot wheezy\-main filesystem:test1:wheezy/daily` + // "FileSystemPublishEndpoints": { - "test1": { - "rootDir": "/opt/srv1/aptly_public", - "linkMethod": "symlink" - }, - "test2": { - "rootDir": "/opt/srv2/aptly_public", - "linkMethod": "copy", - "verifyMethod": "md5" - }, - "test3": { - "rootDir": "/opt/srv3/aptly_public", - "linkMethod": "hardlink" - } + // // Endpoint Name + // "test1": { + // // Directory for publishing + // "rootDir": "/opt/srv/aptly_public", + + // // File Link Method for linking files from the internal pool to the published directory + // // * hardlink + // // * symlink + // // * copy + // "linkMethod": "hardlink", + + // // File Copare Method for comparing existing links from the internal pool to the published directory + // // Only used when "linkMethod" is set to "copy" + // // * md5 (default: compare md5 sum) + // // * size (compare file size) + // "verifyMethod": "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:` + // "S3PublishEndpoints": { - "test": { - "region": "us\-east\-1", - "bucket": "repo", - "endpoint": "", - "awsAccessKeyID": "", - "awsSecretAccessKey": "", - "prefix": "", - "acl": "public\-read", - "storageClass": "", - "encryptionMethod": "", - "plusWorkaround": false, - "disableMultiDel": false, - "forceSigV2": false, - "debug": false - } + // // Endpoint Name + // "test": { + + // // Amazon region for S3 bucket + // "region": "us\-east\-1", + + // // Bucket name + // "bucket": "test\-bucket", + + // // Endpoint (optional) + // // When using S3\-compatible cloud storage, specify hostname of service endpoint here, + // // region is ignored if endpoint is set (set region to some human\-readable name) + // // (should be left blank for real Amazon S3) + // "endpoint": "", + + // // Prefix (optional) + // // publishing under specified prefix in the bucket, defaults to + // // no prefix (bucket root) + // "prefix": "", + + // // Default ACLs (optional) + // // assign ACL to published files (one of the canned ACLs in Amazon + // // terminology)\. Useful values: `private` (default), `public\-read` (public + // // repository) or `none` (don\(cqt set ACL)\. Public repositories could be consumed by `apt` using + // // HTTP endpoint (Amazon bucket should be configured for "website hosting"), + // // for private repositories special apt S3 transport is required\. + // "acl": "private", + + // // Credentials (optional) + // // Amazon credentials to access S3 bucket\. If not supplied, + // // environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` + // // are used\. + // "awsAccessKeyID": "", + // "awsSecretAccessKey": "", + + // // Storage Class (optional) + // // Amazon S3 storage class, defaults to `STANDARD`\. Other values + // // available: `REDUCED_REDUNDANCY` (lower price, lower redundancy) + // "storageClass": "STANDARD", + + // // Encryption Method (optional) + // // Server\-side encryption method, defaults to none\. Currently + // // the only available encryption method is `AES256` + // "encryptionMethod": "none", + + // // Plus Workaround (optional) + // // Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by + // // creating two copies of package files with `+` in filename: one original + // // and another one with spaces instead of plus signs + // // With `plusWorkaround` enabled, package files with plus sign + // // would be stored twice\. aptly might not cleanup files with spaces when published + // // repository is dropped or updated (switched) to new version of repository (snapshot) + // "plusWorkaround": false, + + // // Disable MultiDel (optional) + // // For S3\-compatible cloud storages which do not support `MultiDel` S3 API, + // // enable this setting (file deletion would be slower with this setting enabled) + // "disableMultiDel": false, + + // // ForceSig2 (optional) + // // Disable Signature V4 support, useful with non\-AWS S3\-compatible object stores + // // which do not support SigV4, shouldn\(cqt be enabled for AWS + // "forceSigV2": false, + + // // ForceVirtualHostedStyle (optional) + // // Disable path style visit, useful with non\-AWS S3\-compatible object stores + // // which only support virtual hosted style + // "forceVirtualHostedStyle": false, + + // // Debug (optional) + // // Enables detailed request/response dump for each S3 operation + // "debug": false + // } }, + + // Swift Endpoint Support + // + // aptly could be configured to publish repository directly to OpenStack Swift\. First, + // publishing endpoints should be described in aptly configuration file\. Each endpoint + // has name and associated settings\. + // + // In order to publish to Swift, specify endpoint as `swift:endpoint\-name:` before + // publishing prefix on the command line, e\.g\.: + // + // `aptly publish snapshot jessie\-main swift:test:` + // "SwiftPublishEndpoints": { - "test": { - "container": "repo", - "osname": "", - "password": "", - "prefix": "", - "authurl": "", - "tenant": "", - "tenantid": "" - } + // Endpoint Name + // "test": { + + // // Container Name + // "container": "taylor1", + + // // Prefix (optional) + // // Publish under specified prefix in the container, defaults to no prefix (container root) + // "prefix": "", + + // // Credentials (optional) + // // OpenStack credentials to access Keystone\. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used + // "osname": "", + // "password": "", + + // // Tenant (optional) + // // OpenStack tenant name and id (in order to use v2 authentication) + // "tenant": "", + // "tenantid": "", + + // // Auth URL (optional) + // // Full url of Keystone server (including port, and version)\. + // // Example `http://identity\.example\.com:5000/v2\.0` + // "authurl": "" + // } }, + + // Azure Endpoint Support + // + // aptly can be configured to publish repositories directly to Microsoft Azure Blob + // Storage\. First, publishing endpoints should be described in the aptly + // configuration file\. Each endpoint has its name and associated settings\. "AzurePublishEndpoints": { - "test": { - "accountName": "", - "accountKey": "", - "container": "repo", - "prefix": "" - "endpoint": "blob.core.windows.net" - } + // // Endpoint Name + // "test": { + + // // Container Name + // "container": "container1", + + // // Prefix (optional) + // // Publishing under specified prefix in the container, defaults to no prefix (container root) + // "prefix": "", + + // // Credentials + // // Azure storage account access key to access blob storage + // "accountName": "", + // "accountKey": "", + + // // Endpoint URL + // // See: Azure documentation https://docs\.microsoft\.com/en\-us/azure/storage/common/storage\-configure\-connection\-string + // // defaults to "https://\.blob\.core\.windows\.net" + // "endpoint": "" + // } + }, + + // Package Pool + // Location for storing downloaded packages + // Type must be one of: + // * local + // * azure + "packagePoolStorage": { + // Local Pool + "type": "local", + // Local Pool Path + // empty path defaults to `rootDir`/pool + "path": "" + + // // Azure Azure Blob Storage Pool + // "type": "azure", + // "container": "pool1", + + // // Prefix (optional) + // // Publishing under specified prefix in the container, defaults to no prefix (container root) + // "prefix": "", + + // // Credentials + // // Azure storage account access key to access blob storage + // "accountName": "", + // "accountKey": "", + + // // Endpoint URL + // // See: Azure documentation https://docs\.microsoft\.com/en\-us/azure/storage/common/storage\-configure\-connection\-string + // // defaults to "https://\.blob\.core\.windows\.net" + // "endpoint": "" } + +// End of config } . .fi . .IP "" 0 . -.P -Options: -. -.TP -\fBrootDir\fR -is root of directory storage to store database (\fBrootDir\fR/db), downloaded packages (\fBrootDir\fR/pool) and the default for published repositories (\fBrootDir\fR/public) -. -.TP -\fBdownloadConcurrency\fR -is a number of parallel download threads to use when downloading packages -. -.TP -\fBdownloadSpeedLimit\fR -limit in kbytes/sec on download speed while mirroring remote repositories -. -.TP -\fBdownloadRetries\fR -number of retries for download attempts -. -.TP -\fBdatabaseOpenAttempts\fR -number of attempts to open DB if it\(cqs locked by other instance; could be overridden with option \fB\-db\-open\-attempts\fR -. -.TP -\fBarchitectures\fR -is a list of architectures to process; if left empty defaults to all available architectures; could be overridden with option \fB\-architectures\fR -. -.TP -\fBdependencyFollowSuggests\fR -follow contents of \fBSuggests:\fR field when processing dependencies for the package -. -.TP -\fBdependencyFollowRecommends\fR -follow contents of \fBRecommends:\fR field when processing dependencies for the package -. -.TP -\fBdependencyFollowAllVariants\fR -when dependency looks like \fBpackage\-a | package\-b\fR, follow both variants always -. -.TP -\fBdependencyFollowSource\fR -follow dependency from binary package to source package -. -.TP -\fBdependencyVerboseResolve\fR -print additional details while resolving dependencies (useful for debugging) -. -.TP -\fBgpgDisableSign\fR -don\(cqt sign published repositories with gpg(1), also can be disabled on per\-repo basis using \fB\-skip\-signing\fR flag when publishing -. -.TP -\fBgpgDisableVerify\fR -don\(cqt verify remote mirrors with gpg(1), also can be disabled on per\-mirror basis using \fB\-ignore\-signatures\fR flag when creating and updating mirrors -. -.TP -\fBgpgProvider\fR -implementation of PGP signing/validation \- \fBgpg\fR for external \fBgpg\fR utility or \fBinternal\fR to use Go internal implementation; \fBgpg1\fR might be used to force use of GnuPG 1\.x, \fBgpg2\fR enables GnuPG 2\.x only; default is to use GnuPG 1\.x if available and GnuPG 2\.x otherwise -. -.TP -\fBdownloadSourcePackages\fR -if enabled, all mirrors created would have flag set to download source packages; this setting could be controlled on per\-mirror basis with \fB\-with\-sources\fR flag -. -.TP -\fBskipLegacyPool\fR -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; if option is enabled, aptly stops checking for legacy paths; by default option is enabled for new aptly installations and disabled when upgrading from older versions -. -.TP -\fBppaDistributorID\fR, \fBppaCodename\fR -specifies paramaters for short PPA url expansion, if left blank they default to output of \fBlsb_release\fR command -. -.TP -\fBFileSystemPublishEndpoints\fR -configuration of local filesystem publishing endpoints (see below) -. -.TP -\fBS3PublishEndpoints\fR -configuration of Amazon S3 publishing endpoints (see below) -. -.TP -\fBSwiftPublishEndpoints\fR -configuration of OpenStack Swift publishing endpoints (see below) -. -.SH "FILESYSTEM PUBLISHING ENDPOINTS" -aptly defaults to publish to a single publish directory under \fBrootDir\fR/public\. For a more advanced publishing strategy, you can define one or more filesystem endpoints in the \fBFileSystemPublishEndpoints\fR list of the aptly configuration file\. Each endpoint has a name and the following associated settings: -. -.TP -\fBrootDir\fR -The publish directory, e\.g\., \fB/opt/srv/aptly_public\fR\. -. -.TP -\fBlinkMethod\fR -This is one of \fBhardlink\fR, \fBsymlink\fR or \fBcopy\fR\. It specifies how aptly links the files from the internal pool to the published directory\. If not specified, empty or wrong, this defaults to \fBhardlink\fR\. -. -.TP -\fBverifyMethod\fR -This is used only when setting the \fBlinkMethod\fR to \fBcopy\fR\. Possible values are \fBmd5\fR and \fBsize\fR\. It specifies how aptly compares existing links from the internal pool to the published directory\. The \fBsize\fR method compares only the file sizes, whereas the \fBmd5\fR method calculates the md5 checksum of the found file and compares it to the desired one\. If not specified, empty or wrong, this defaults to \fBmd5\fR\. -. -.P -In order to publish to such an endpoint, specify the endpoint as \fBfilesystem:endpoint\-name\fR with \fBendpoint\-name\fR as the name given in the aptly configuration file\. For example: -. -.P -\fBaptly publish snapshot wheezy\-main filesystem:test1:wheezy/daily\fR -. -.SH "S3 PUBLISHING ENDPOINTS" -aptly could be configured to publish repository directly to Amazon S3 (or S3\-compatible cloud storage)\. First, publishing endpoints should be described in aptly configuration file\. Each endpoint has name and associated settings: -. -.TP -\fBregion\fR -Amazon region for S3 bucket (e\.g\. \fBus\-east\-1\fR) -. -.TP -\fBbucket\fR -bucket name -. -.TP -\fBendpoint\fR -(optional) when using S3\-compatible cloud storage, specify hostname of service endpoint here, region is ignored if endpoint is set (set region to some human\-readable name) (should be left blank for real Amazon S3) -. -.TP -\fBprefix\fR -(optional) do publishing under specified prefix in the bucket, defaults to no prefix (bucket root) -. -.TP -\fBacl\fR -(optional) assign ACL to published files (one of the canned ACLs in Amazon terminology)\. Useful values: \fBprivate\fR (default), \fBpublic\-read\fR (public repository) or \fBnone\fR (don\(cqt set ACL)\. Public repositories could be consumed by \fBapt\fR using HTTP endpoint (Amazon bucket should be configured for "website hosting"), for private repositories special apt S3 transport is required\. -. -.TP -\fBawsAccessKeyID\fR, \fBawsSecretAccessKey\fR -(optional) Amazon credentials to access S3 bucket\. If not supplied, environment variables \fBAWS_ACCESS_KEY_ID\fR and \fBAWS_SECRET_ACCESS_KEY\fR are used\. -. -.TP -\fBstorageClass\fR -(optional) Amazon S3 storage class, defaults to \fBSTANDARD\fR\. Other values available: \fBREDUCED_REDUNDANCY\fR (lower price, lower redundancy) -. -.TP -\fBencryptionMethod\fR -(optional) server\-side encryption method, defaults to none\. Currently the only available encryption method is \fBAES256\fR -. -.TP -\fBplusWorkaround\fR -(optional) workaround misbehavior in apt and Amazon S3 for files with \fB+\fR in filename by creating two copies of package files with \fB+\fR in filename: one original and another one with spaces instead of plus signs With \fBplusWorkaround\fR enabled, package files with plus sign would be stored twice\. aptly might not cleanup files with spaces when published repository is dropped or updated (switched) to new version of repository (snapshot) -. -.TP -\fBdisableMultiDel\fR -(optional) for S3\-compatible cloud storages which do not support \fBMultiDel\fR S3 API, enable this setting (file deletion would be slower with this setting enabled) -. -.TP -\fBforceSigV2\fR -(optional) disable Signature V4 support, useful with non\-AWS S3\-compatible object stores which do not support SigV4, shouldn\(cqt be enabled for AWS -. -.TP -\fBdebug\fR -(optional) enables detailed request/response dump for each S3 operation -. -.P -In order to publish to S3, specify endpoint as \fBs3:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.: -. -.P -\fBaptly publish snapshot wheezy\-main s3:test:\fR -. -.SH "OPENSTACK SWIFT PUBLISHING ENDPOINTS" -aptly could be configured to publish repository directly to OpenStack Swift\. First, publishing endpoints should be described in aptly configuration file\. Each endpoint has name and associated settings: -. -.TP -\fBcontainer\fR -container name -. -.TP -\fBprefix\fR -(optional) do publishing under specified prefix in the container, defaults to no prefix (container root) -. -.TP -\fBosname\fR, \fBpassword\fR -(optional) OpenStack credentials to access Keystone\. If not supplied, environment variables \fBOS_USERNAME\fR and \fBOS_PASSWORD\fR are used\. -. -.TP -\fBtenant\fR, \fBtenantid\fR -(optional) OpenStack tenant name and id (in order to use v2 authentication)\. -. -.TP -\fBauthurl\fR -(optional) the full url of Keystone server (including port, and version)\. example \fBhttp://identity\.example\.com:5000/v2\.0\fR -. -.P -In order to publish to Swift, specify endpoint as \fBswift:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.: -. -.P -\fBaptly publish snapshot jessie\-main swift:test:\fR -. .SH "PACKAGE QUERY" Some commands accept package queries to identify list of packages to process\. Package query syntax almost matches \fBreprepro\fR query language\. Query consists of the following simple terms: . @@ -426,7 +535,7 @@ list of architectures to consider during (comma\-separated), default to all avai . .TP \-\fBconfig\fR= -location of configuration file (default locations are /etc/aptly\.conf, ~/\.aptly\.conf) +location of configuration file (default locations in order: ~/\.aptly\.conf, /usr/local/etc/aptly\.conf, /etc/aptly\.conf) . .TP \-\fBdb\-open\-attempts\fR=10 @@ -479,7 +588,7 @@ Options: . .TP \-\fBfilter\fR= -filter packages in mirror +filter packages in mirror, use \(cq@file\(cq to read filter from file or \(cq@\-\(cq for stdin . .TP \-\fBfilter\-with\-deps\fR @@ -502,6 +611,10 @@ disable verification of Release file signatures gpg keyring to use when verifying Release file (could be specified multiple times) . .TP +\-\fBmax\-tries\fR=1 +max download tries till process fails with download error +. +.TP \-\fBwith\-installer\fR download additional not packaged installer files . @@ -658,7 +771,7 @@ archive url is the root of archive . .TP \-\fBfilter\fR= -filter packages in mirror +filter packages in mirror, use \(cq@file\(cq to read filter from file or \(cq@\-\(cq for stdin . .TP \-\fBfilter\-with\-deps\fR @@ -718,7 +831,7 @@ custom format for result printing include dependencies into search results . .SH "ADD PACKAGES TO LOCAL REPOSITORY" -\fBaptly\fR \fBrepo\fR \fBadd\fR \fIname\fR +\fBaptly\fR \fBrepo\fR \fBadd\fR \fIname\fR \fB(|)\|\.\|\.\|\.\fR . .P Command adds packages to local repository from \.deb, \.udeb (binary packages) and \.dsc (source packages) files\. When importing from directory aptly would do recursive scan looking for all files matching \fI\.[u]deb or\fR\.dsc patterns\. Every file discovered would be analyzed to extract metadata, package would then be created and added to the database\. Files would be imported to internal package pool\. For source packages, all required files are added automatically as well\. Extra files for source package should be in the same directory as *\.dsc file\. @@ -903,6 +1016,9 @@ display list in machine\-readable format Command move moves packages matching \fIpackage\-query\fR from local repo \fIsrc\-name\fR to local repo \fIdst\-name\fR\. . .P +Use \(cq@file\(cq to read package queries from file or \(cq@\-\(cq for stdin\. +. +.P Example: . .P @@ -926,6 +1042,9 @@ follow dependencies when processing package\-spec Commands removes packages matching \fIpackage\-query\fR from local repository \fIname\fR\. If removed packages are not referenced by other repos or snapshots, they can be removed completely (including files) by running \(cqaptly db cleanup\(cq\. . .P +Use \(cq@file\(cq to read package queries from file or \(cq@\-\(cq for stdin\. +. +.P Example: . .P @@ -1004,13 +1123,13 @@ custom format for result printing include dependencies into search results . .SH "ADD PACKAGES TO LOCAL REPOSITORIES BASED ON \.CHANGES FILES" -\fBaptly\fR \fBrepo\fR \fBinclude\fR |\fIdirectory\fR \fB\|\.\|\.\|\.\fR +\fBaptly\fR \fBrepo\fR \fBinclude\fR \fB(|)\|\.\|\.\|\.\fR . .P Command include looks for \.changes files in list of arguments or specified directories\. Each \.changes file is verified, parsed, referenced files are put into separate temporary directory and added into local repository\. Successfully imported files are removed by default\. . .P -Additionally uploads could be restricted with file\. Rules in this file control uploads based on GPG key ID of \.changes file signature and queries on \.changes file fields\. +Additionally uploads could be restricted with \(cquploaders\.json\(cq file\. Rules in this file control uploads based on GPG key ID of \.changes file signature and queries on \.changes file fields\. . .P Example: @@ -1050,7 +1169,7 @@ which repo should files go to, defaults to Distribution field of \.changes file path to uploaders\.json file . .SH "CREATES SNAPSHOT OF MIRROR (LOCAL REPOSITORY) CONTENTS" -\fBaptly\fR \fBsnapshot\fR \fBcreate\fR \fIname\fR \fBfrom\fR \fBmirror\fR \fImirror\-name\fR \fB|\fR \fBfrom\fR \fBrepo\fR \fIrepo\-name\fR \fB|\fR \fBempty\fR +\fBaptly\fR \fBsnapshot\fR \fBcreate\fR \fIname\fR \fB(from\fR \fBmirror\fR \fImirror\-name\fR \fB|\fR \fBfrom\fR \fBrepo\fR \fIrepo\-name\fR \fB|\fR \fBempty)\fR . .P Command create \fIname\fR from mirror makes persistent immutable snapshot of remote repository mirror\. Snapshot could be published or further modified using merge, pull and other aptly features\. @@ -1150,6 +1269,9 @@ $ aptly snapshot verify wheezy\-main wheezy\-contrib wheezy\-non\-free Command pull pulls new packages along with its\(cq dependencies to snapshot \fIname\fR from snapshot \fIsource\fR\. Pull can upgrade package version in \fIname\fR with versions from \fIsource\fR following dependencies\. New snapshot \fIdestination\fR is created as a result of this process\. Packages could be specified simply as \(cqpackage\-name\(cq or as package queries\. . .P +Use \(cq@file\(cq syntax to read package queries from file and \(cq@\-\(cq to read from stdin\. +. +.P Example: . .IP "" 4 @@ -1285,6 +1407,9 @@ Command search displays list of packages in snapshot that match package query If query is not specified, all the packages are displayed\. . .P +Use \(cq@file\(cq syntax to read package query from file and \(cq@\-\(cq to read from stdin\. +. +.P Example: . .IP "" 4 @@ -1315,6 +1440,9 @@ include dependencies into search results Command filter does filtering in snapshot \fIsource\fR, producing another snapshot \fIdestination\fR\. Packages could be specified simply as \(cqpackage\-name\(cq or as package queries\. . .P +Use \(cq@file\(cq syntax to read package queries from file and \(cq@\-\(cq to read from stdin\. +. +.P Example: . .IP "" 4 @@ -1445,6 +1573,10 @@ run GPG with detached tty set value for ButAutomaticUpgrades field . .TP +\-\fBcodename\fR= +codename to publish (defaults to distribution) +. +.TP \-\fBcomponent\fR= component name to publish (for multi\-component publishing, separate components with commas) . @@ -1469,6 +1601,10 @@ GPG keyring to use (instead of default) label to publish . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBnotautomatic\fR= set value for NotAutomatic field . @@ -1489,6 +1625,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-contents\fR don\(cqt generate Contents indexes . @@ -1500,6 +1640,32 @@ don\(cqt sign Release files with GPG \-\fBsuite\fR= suite to publish (defaults to distribution) . +.SH "SHOWS DETAILS OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBshow\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] +. +.P +Command show displays full information of a published repository\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish show wheezy +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBjson\fR +display record in JSON format +. .SH "PUBLISH SNAPSHOT" \fBaptly\fR \fBpublish\fR \fBsnapshot\fR \fIname\fR [[\fIendpoint\fR:]\fIprefix\fR] . @@ -1548,6 +1714,10 @@ run GPG with detached tty overwrite value for ButAutomaticUpgrades field . .TP +\-\fBcodename\fR= +codename to publish (defaults to distribution) +. +.TP \-\fBcomponent\fR= component name to publish (for multi\-component publishing, separate components with commas) . @@ -1572,6 +1742,10 @@ GPG keyring to use (instead of default) label to publish . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBnotautomatic\fR= overwrite value for NotAutomatic field . @@ -1592,6 +1766,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-contents\fR don\(cqt generate Contents indexes . @@ -1603,14 +1781,255 @@ don\(cqt sign Release files with GPG \-\fBsuite\fR= suite to publish (defaults to distribution) . -.SH "UPDATE PUBLISHED REPOSITORY BY SWITCHING TO NEW SNAPSHOT" -\fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-snapshot\fR +.SH "ADD SOURCE COMPONENTS TO A PUBLISHED REPO" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBadd\fR \fIdistribution\fR \fIsource\fR . .P -Command switches in\-place published snapshots with new snapshot contents\. All publishing parameters are preserved (architecture list, distribution, component)\. +The command adds components of a snapshot or local repository to be published\. . .P -For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding snapshots should be given in the same order, e\.g\.: +This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\. +. +.P +The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be modified\. The number of given components must be equal to the number of given sources, e\.g\.: +. +.IP "" 4 +. +.nf + +aptly publish source add \-component=main,contrib wheezy wheezy\-main wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source add \-component=contrib wheezy ppa wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +This command assigns the snapshot wheezy\-contrib to the component contrib and adds it to published repository revision of ppa/wheezy\. +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "DROP PENDING SOURCE COMPONENT CHANGES OF A PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBdrop\fR \fIdistribution\fR +. +.P +Remove all pending changes what would be applied with a subsequent \(cqaptly publish update\(cq\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source drop wheezy +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "LISTS REVISION OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBlist\fR \fIdistribution\fR +. +.P +Command lists sources of a published repository\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source list wheezy +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBjson\fR +display record in JSON format +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "REMOVE SOURCE COMPONENTS FROM A PUBLISHED REPO" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBremove\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fIsource\fR +. +.P +The command removes source components (snapshot / local repo) from a published repository\. +. +.P +This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\. +. +.P +The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be removed, e\.g\.: +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source remove \-component=contrib,non\-free wheezy filesystem:symlink:debian +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to remove (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "REPLACE THE SOURCE COMPONENTS OF A PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBreplace\fR \fIdistribution\fR \fIsource\fR +. +.P +The command replaces the source components of a snapshot or local repository to be published\. +. +.P +This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\. +. +.P +The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be modified\. The number of given components must be equal to the number of given sources, e\.g\.: +. +.IP "" 4 +. +.nf + +aptly publish source replace \-component=main,contrib wheezy wheezy\-main wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source replace \-component=contrib wheezy ppa wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "UPDATE THE SOURCE COMPONENTS OF A PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBupdate\fR \fIdistribution\fR \fIsource\fR +. +.P +The command updates the source components of a snapshot or local repository to be published\. +. +.P +This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\. +. +.P +The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be modified\. The number of given components must be equal to the number of given sources, e\.g\.: +. +.IP "" 4 +. +.nf + +aptly publish source update \-component=main,contrib wheezy wheezy\-main wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source update \-component=contrib wheezy ppa wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "UPDATE PUBLISHED REPOSITORY BY SWITCHING TO NEW SOURCE" +\fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-source\fR +. +.P +Command switches in\-place published snapshots with new source contents\. All publishing parameters are preserved (architecture list, distribution, component)\. +. +.P +For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding sources should be given in the same order, e\.g\.: . .IP "" 4 . @@ -1662,6 +2081,10 @@ GPG key ID to use when signing the release GPG keyring to use (instead of default) . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBpassphrase\fR= GPG passphrase for the key (warning: could be insecure) . @@ -1674,6 +2097,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-cleanup\fR don\(cqt remove unreferenced files in prefix/component . @@ -1685,11 +2112,40 @@ don\(cqt generate Contents indexes \-\fBskip\-signing\fR don\(cqt sign Release files with GPG . -.SH "UPDATE PUBLISHED LOCAL REPOSITORY" +.SH "UPDATE PUBLISHED REPOSITORY" \fBaptly\fR \fBpublish\fR \fBupdate\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] . .P -Command re\-publishes (updates) published local repository\. \fIdistribution\fR and \fIprefix\fR should be occupied with local repository published using command aptly publish repo\. Update happens in\-place with minimum possible downtime for published repository\. +The command updates updates a published repository after applying pending changes to the sources\. +. +.P +For published local repositories: +. +.IP "" 4 +. +.nf + +* update to match local repository contents +. +.fi +. +.IP "" 0 +. +.P +For published snapshots: +. +.IP "" 4 +. +.nf + +* switch components to new snapshot +. +.fi +. +.IP "" 0 +. +.P +The update happens in\-place with minimum possible downtime for published repository\. . .P For multiple component published repositories, all local repositories are updated\. @@ -1727,6 +2183,10 @@ GPG key ID to use when signing the release GPG keyring to use (instead of default) . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBpassphrase\fR= GPG passphrase for the key (warning: could be insecure) . @@ -1739,6 +2199,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-cleanup\fR don\(cqt remove unreferenced files in prefix/component . @@ -1750,32 +2214,6 @@ don\(cqt generate Contents indexes \-\fBskip\-signing\fR don\(cqt sign Release files with GPG . -.SH "SHOWS DETAILS OF PUBLISHED REPOSITORY" -\fBaptly\fR \fBpublish\fR \fBshow\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] -. -.P -Command show displays full information of a published repository\. -. -.P -Example: -. -.IP "" 4 -. -.nf - -$ aptly publish show wheezy -. -.fi -. -.IP "" 0 -. -.P -Options: -. -.TP -\-\fBjson\fR -display record in JSON format -. .SH "SEARCH FOR PACKAGES MATCHING QUERY" \fBaptly\fR \fBpackage\fR \fBsearch\fR [\fIpackage\-query\fR] . @@ -1783,7 +2221,7 @@ display record in JSON format Command search displays list of packages in whole DB that match package query\. . .P -If query is not specified, all the packages are displayed\. +Use \(cq@file\(cq to read query from file or \(cq@\-\(cq for stdin\. If query is not specified, all the packages are displayed\. . .P Example: @@ -1812,6 +2250,9 @@ custom format for result printing Command shows displays detailed meta\-information about packages matching query\. Information from Debian control file is displayed\. Optionally information about package files and inclusion into mirrors/snapshots/local repos is shown\. . .P +Use \(cq@file\(cq to read query from file or \(cq@\-\(cq for stdin\. +. +.P Example: . .IP "" 4 @@ -1951,8 +2392,15 @@ Example: .P $ aptly config show . +.P +Options: +. +.TP +\-\fByaml\fR +show yaml config +. .SH "RUN APTLY TASKS" -\fBaptly\fR \fBtask\fR \fBrun\fR \-filename=\fIfilename\fR \fB|\fR \fIcommand1\fR, \fIcommand2\fR, \fB\|\.\|\.\|\.\fR +\fBaptly\fR \fBtask\fR \fBrun\fR (\-filename=\fIfilename\fR \fB|\fR \fIcommands\fR\|\.\|\.\|\.) . .P Command helps organise multiple aptly commands in one single aptly task, running as single thread\. @@ -1994,6 +2442,13 @@ Example: .P $ aptly config show . +.P +Options: +. +.TP +\-\fByaml\fR +show yaml config +. .SH "ENVIRONMENT" If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\. . @@ -2147,5 +2602,77 @@ Lorenzo Bolla (https://github\.com/lbolla) .IP "\[ci]" 4 Benj Fassbind (https://github\.com/randombenj) . +.IP "\[ci]" 4 +Markus Muellner (https://github\.com/mmianl) +. +.IP "\[ci]" 4 +Chuan Liu (https://github\.com/chuan) +. +.IP "\[ci]" 4 +Samuel Mutel (https://github\.com/smutel) +. +.IP "\[ci]" 4 +Russell Greene (https://github\.com/russelltg) +. +.IP "\[ci]" 4 +Wade Simmons (https://github\.com/wadey) +. +.IP "\[ci]" 4 +Steven Stone (https://github\.com/smstone) +. +.IP "\[ci]" 4 +Josh Bayfield (https://github\.com/jbayfield) +. +.IP "\[ci]" 4 +Boxjan (https://github\.com/boxjan) +. +.IP "\[ci]" 4 +Mauro Regli (https://github\.com/reglim) +. +.IP "\[ci]" 4 +Alexander Zubarev (https://github\.com/strike) +. +.IP "\[ci]" 4 +Nicolas Dostert (https://github\.com/acdn\-ndostert) +. +.IP "\[ci]" 4 +Ryan Gonzalez (https://github\.com/refi64) +. +.IP "\[ci]" 4 +Paul Cacheux (https://github\.com/paulcacheux) +. +.IP "\[ci]" 4 +Nic Waller (https://github\.com/sf\-nwaller) +. +.IP "\[ci]" 4 +iofq (https://github\.com/iofq) +. +.IP "\[ci]" 4 +Noa Resare (https://github\.com/nresare) +. +.IP "\[ci]" 4 +Ramon N\.Rodriguez (https://github\.com/runitonmetal) +. +.IP "\[ci]" 4 +Golf Hu (https://github\.com/hudeng\-go) +. +.IP "\[ci]" 4 +Cookie Fei (https://github\.com/wuhuang26) +. +.IP "\[ci]" 4 +Andrey Loukhnov (https://github\.com/aol\-nnov) +. +.IP "\[ci]" 4 +Christoph Fiehe (https://github\.com/cfiehe) +. +.IP "\[ci]" 4 +Blake Kostner (https://github\.com/btkostner) +. +.IP "\[ci]" 4 +Leigh London (https://github\.com/leighlondon) +. +.IP "\[ci]" 4 +Gordian Schoenherr (https://github\.com/schoenherrg) +. .IP "" 0 diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl index 82cc1dfc..203cc7fe 100644 --- a/man/aptly.1.ronn.tmpl +++ b/man/aptly.1.ronn.tmpl @@ -18,271 +18,385 @@ aptly has integrated help that matches contents of this manual page, to get help ## CONFIGURATION -aptly looks for configuration file first in `~/.aptly.conf` then -in `/etc/aptly.conf` and, if no config file found, new one is created in -home directory. If `-config=` flag is specified, aptly would use config file at specified -location. Also aptly needs root directory for database, package and published repository storage. -If not specified, directory defaults to `~/.aptly`, it will be created if missing. +aptly looks for configuration file first in `~/.aptly.conf` then in `/usr/local/etc/aptly.conf` and `/etc/aptly.conf`. If no config file found (or they are not readable), a new one is created in the +home directory. If `-config=` flag is specified, aptly would use config file at specified location. Also aptly needs root directory for database, package and published repository storage. If not specified, directory defaults to `~/.aptly/`, it will be created if missing. -Configuration file is stored in JSON format (default values shown below): +With aptly version 1.6.0, yaml configuration with inline documentation is +supported and recommended (see `debian/aptly.conf`). +The legacy json configuration is still supported (and also supports comments): + + // vim: : filetype=json + // json configuration file with comments + // validate with: sed '/\/\//d' aptly.conf | json_pp { - "rootDir": "$HOME/.aptly", - "downloadConcurrency": 4, - "downloadSpeedLimit": 0, - "downloadRetries": 0, - "downloader": "default", - "databaseOpenAttempts": 10, + + // Aptly Configuration File + //////////////////////////// + + // Root directory for: + // - downloaded packages (`rootDir`/pool) + // - database (`rootDir`/db) + // - published repositories (`rootDir`/public) + "rootDir": "~/.aptly", + + // Number of attempts to open database if it's locked by other instance + // * -1 (no retry) + "databaseOpenAttempts": -1, + + // Log Level + // * debug + // * info + // * warning + // * error + "logLevel": "info", + + // Log Format + // * default (text) + // * json + "logFormat": "default", + + // Default Architectures + // empty array defaults to all available architectures "architectures": [], + + // Follow contents of `Suggests:` field when processing dependencies for the package "dependencyFollowSuggests": false, + + // Follow contents of `Recommends:` field when processing dependencies for the package "dependencyFollowRecommends": false, + + // When dependency looks like `package-a | package-b`, follow both variants always "dependencyFollowAllVariants": false, + + // Follow dependency from binary package to source package "dependencyFollowSource": false, + + // Log additional details while resolving dependencies (useful for debugging) "dependencyVerboseResolve": false, - "gpgDisableSign": false, - "gpgDisableVerify": false, - "gpgProvider": "gpg", - "downloadSourcePackages": false, - "skipLegacyPool": true, + + // Specifies paramaters for short PPA url expansion + // empty defaults to output of `lsb_release` command "ppaDistributorID": "ubuntu", + + // Codename for short PPA url expansion "ppaCodename": "", + + // 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; + // if option is enabled, aptly stops checking for legacy paths; + // by default option is enabled for new aptly installations and disabled when + // upgrading from older versions + "skipLegacyPool": true, + + + // Aptly Server + //////////////// + + // Serve published repos as well as API + "serveInAPIMode": false, + + // Enable metrics for Prometheus client + "enableMetricsEndpoint": false, + + // Enable API documentation on /docs + "enableSwaggerEndpoint": false, + + // OBSOLETE: use via url param ?_async=true + "AsyncAPI": false, + + + // Database + //////////// + + // Database backend + // Type must be one of: + // * leveldb (default) + // * etcd + "databaseBackend": { + // LevelDB + "type": "leveldb", + // Path to leveldb files + // empty dbPath defaults to `rootDir`/db + "dbPath": "" + + // // etcd + // "type": "etcd", + // // URL to db server + // "url": "127.0.0.1:2379" + }, + + + // Mirroring + ///////////// + + // Downloader + // * "default" + // * "grab" (more robust) + "downloader": "default", + + // Number of parallel download threads to use when downloading packages + "downloadConcurrency": 4, + + // Limit in kbytes/sec on download speed while mirroring remote repositories + "downloadSpeedLimit": 0, + + // Number of retries for download attempts + "downloadRetries": 0, + + // Download source packages per default + "downloadSourcePackages": false, + + + // Signing + /////////// + + // GPG Provider + // * "internal" (Go internal implementation) + // * "gpg" (External `gpg` utility) + "gpgProvider": "gpg", + + // Disable signing of published repositories + "gpgDisableSign": false, + + // Disable signature verification of remote repositories + "gpgDisableVerify": false, + + + // Publishing + ////////////// + + // Do not publish Contents files "skipContentsPublishing": false, + + // Do not create bz2 files + "skipBz2Publishing": false, + + + // Storage + /////////// + + // Filesystem publishing endpoints + // + // aptly defaults to publish to a single publish directory under `rootDir`/public. For + // a more advanced publishing strategy, you can define one or more filesystem endpoints in the + // `FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name + // and the following associated settings. + // + // In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name` + // with `endpoint-name` as the name given in the aptly configuration file. For example: + // + // `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily` + // "FileSystemPublishEndpoints": { - "test1": { - "rootDir": "/opt/srv1/aptly_public", - "linkMethod": "symlink" - }, - "test2": { - "rootDir": "/opt/srv2/aptly_public", - "linkMethod": "copy", - "verifyMethod": "md5" - }, - "test3": { - "rootDir": "/opt/srv3/aptly_public", - "linkMethod": "hardlink" - } + // // Endpoint Name + // "test1": { + // // Directory for publishing + // "rootDir": "/opt/srv/aptly_public", + + // // File Link Method for linking files from the internal pool to the published directory + // // * hardlink + // // * symlink + // // * copy + // "linkMethod": "hardlink", + + // // File Copare Method for comparing existing links from the internal pool to the published directory + // // Only used when "linkMethod" is set to "copy" + // // * md5 (default: compare md5 sum) + // // * size (compare file size) + // "verifyMethod": "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:` + // "S3PublishEndpoints": { - "test": { - "region": "us-east-1", - "bucket": "repo", - "endpoint": "", - "awsAccessKeyID": "", - "awsSecretAccessKey": "", - "prefix": "", - "acl": "public-read", - "storageClass": "", - "encryptionMethod": "", - "plusWorkaround": false, - "disableMultiDel": false, - "forceSigV2": false, - "debug": false - } + // // Endpoint Name + // "test": { + + // // Amazon region for S3 bucket + // "region": "us-east-1", + + // // Bucket name + // "bucket": "test-bucket", + + // // Endpoint (optional) + // // When using S3-compatible cloud storage, specify hostname of service endpoint here, + // // region is ignored if endpoint is set (set region to some human-readable name) + // // (should be left blank for real Amazon S3) + // "endpoint": "", + + // // Prefix (optional) + // // publishing under specified prefix in the bucket, defaults to + // // no prefix (bucket root) + // "prefix": "", + + // // Default ACLs (optional) + // // assign ACL to published files (one of the canned ACLs in Amazon + // // terminology). Useful values: `private` (default), `public-read` (public + // // repository) or `none` (don't set ACL). Public repositories could be consumed by `apt` using + // // HTTP endpoint (Amazon bucket should be configured for "website hosting"), + // // for private repositories special apt S3 transport is required. + // "acl": "private", + + // // Credentials (optional) + // // Amazon credentials to access S3 bucket. If not supplied, + // // environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` + // // are used. + // "awsAccessKeyID": "", + // "awsSecretAccessKey": "", + + // // Storage Class (optional) + // // Amazon S3 storage class, defaults to `STANDARD`. Other values + // // available: `REDUCED_REDUNDANCY` (lower price, lower redundancy) + // "storageClass": "STANDARD", + + // // Encryption Method (optional) + // // Server-side encryption method, defaults to none. Currently + // // the only available encryption method is `AES256` + // "encryptionMethod": "none", + + // // Plus Workaround (optional) + // // Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by + // // creating two copies of package files with `+` in filename: one original + // // and another one with spaces instead of plus signs + // // With `plusWorkaround` enabled, package files with plus sign + // // would be stored twice. aptly might not cleanup files with spaces when published + // // repository is dropped or updated (switched) to new version of repository (snapshot) + // "plusWorkaround": false, + + // // Disable MultiDel (optional) + // // For S3-compatible cloud storages which do not support `MultiDel` S3 API, + // // enable this setting (file deletion would be slower with this setting enabled) + // "disableMultiDel": false, + + // // ForceSig2 (optional) + // // Disable Signature V4 support, useful with non-AWS S3-compatible object stores + // // which do not support SigV4, shouldn't be enabled for AWS + // "forceSigV2": false, + + // // ForceVirtualHostedStyle (optional) + // // Disable path style visit, useful with non-AWS S3-compatible object stores + // // which only support virtual hosted style + // "forceVirtualHostedStyle": false, + + // // Debug (optional) + // // Enables detailed request/response dump for each S3 operation + // "debug": false + // } }, + + // Swift Endpoint Support + // + // aptly could be configured to publish repository directly to OpenStack Swift. First, + // publishing endpoints should be described in aptly configuration file. Each endpoint + // has name and associated settings. + // + // In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before + // publishing prefix on the command line, e.g.: + // + // `aptly publish snapshot jessie-main swift:test:` + // "SwiftPublishEndpoints": { - "test": { - "container": "repo", - "osname": "", - "password": "", - "prefix": "", - "authurl": "", - "tenant": "", - "tenantid": "" - } + // Endpoint Name + // "test": { + + // // Container Name + // "container": "taylor1", + + // // Prefix (optional) + // // Publish under specified prefix in the container, defaults to no prefix (container root) + // "prefix": "", + + // // Credentials (optional) + // // OpenStack credentials to access Keystone. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used + // "osname": "", + // "password": "", + + // // Tenant (optional) + // // OpenStack tenant name and id (in order to use v2 authentication) + // "tenant": "", + // "tenantid": "", + + // // Auth URL (optional) + // // Full url of Keystone server (including port, and version). + // // Example `http://identity.example.com:5000/v2.0` + // "authurl": "" + // } }, + + // Azure Endpoint Support + // + // aptly can be configured to publish repositories directly to Microsoft Azure Blob + // Storage. First, publishing endpoints should be described in the aptly + // configuration file. Each endpoint has its name and associated settings. "AzurePublishEndpoints": { - "test": { - "accountName": "", - "accountKey": "", - "container": "repo", - "prefix": "" - "endpoint": "blob.core.windows.net" - } + // // Endpoint Name + // "test": { + + // // Container Name + // "container": "container1", + + // // Prefix (optional) + // // Publishing under specified prefix in the container, defaults to no prefix (container root) + // "prefix": "", + + // // Credentials + // // Azure storage account access key to access blob storage + // "accountName": "", + // "accountKey": "", + + // // Endpoint URL + // // See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string + // // defaults to "https://.blob.core.windows.net" + // "endpoint": "" + // } + }, + + // Package Pool + // Location for storing downloaded packages + // Type must be one of: + // * local + // * azure + "packagePoolStorage": { + // Local Pool + "type": "local", + // Local Pool Path + // empty path defaults to `rootDir`/pool + "path": "" + + // // Azure Azure Blob Storage Pool + // "type": "azure", + // "container": "pool1", + + // // Prefix (optional) + // // Publishing under specified prefix in the container, defaults to no prefix (container root) + // "prefix": "", + + // // Credentials + // // Azure storage account access key to access blob storage + // "accountName": "", + // "accountKey": "", + + // // Endpoint URL + // // See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string + // // defaults to "https://.blob.core.windows.net" + // "endpoint": "" } + + // End of config } -Options: - - * `rootDir`: - is root of directory storage to store database (`rootDir`/db), downloaded packages (`rootDir`/pool) and - the default for published repositories (`rootDir`/public) - - * `downloadConcurrency`: - is a number of parallel download threads to use when downloading packages - - * `downloadSpeedLimit`: - limit in kbytes/sec on download speed while mirroring remote repositories - - * `downloadRetries`: - number of retries for download attempts - - * `databaseOpenAttempts`: - number of attempts to open DB if it's locked by other instance; could be overridden with option - `-db-open-attempts` - - * `architectures`: - is a list of architectures to process; if left empty defaults to all available architectures; could be - overridden with option `-architectures` - - * `dependencyFollowSuggests`: - follow contents of `Suggests:` field when processing dependencies for the package - - * `dependencyFollowRecommends`: - follow contents of `Recommends:` field when processing dependencies for the package - - * `dependencyFollowAllVariants`: - when dependency looks like `package-a | package-b`, follow both variants always - - * `dependencyFollowSource`: - follow dependency from binary package to source package - - * `dependencyVerboseResolve`: - print additional details while resolving dependencies (useful for debugging) - - * `gpgDisableSign`: - don't sign published repositories with gpg(1), also can be disabled on - per-repo basis using `-skip-signing` flag when publishing - - * `gpgDisableVerify`: - don't verify remote mirrors with gpg(1), also can be disabled on - per-mirror basis using `-ignore-signatures` flag when creating and updating mirrors - - * `gpgProvider`: - implementation of PGP signing/validation - `gpg` for external `gpg` utility or - `internal` to use Go internal implementation; `gpg1` might be used to force use - of GnuPG 1.x, `gpg2` enables GnuPG 2.x only; default is to use GnuPG 1.x if - available and GnuPG 2.x otherwise - - * `downloadSourcePackages`: - if enabled, all mirrors created would have flag set to download source packages; - this setting could be controlled on per-mirror basis with `-with-sources` flag - - * `skipLegacyPool`: - 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; - if option is enabled, aptly stops checking for legacy paths; - by default option is enabled for new aptly installations and disabled when - upgrading from older versions - - * `ppaDistributorID`, `ppaCodename`: - specifies paramaters for short PPA url expansion, if left blank they default - to output of `lsb_release` command - - * `FileSystemPublishEndpoints`: - configuration of local filesystem publishing endpoints (see below) - - * `S3PublishEndpoints`: - configuration of Amazon S3 publishing endpoints (see below) - - * `SwiftPublishEndpoints`: - configuration of OpenStack Swift publishing endpoints (see below) - -## FILESYSTEM PUBLISHING ENDPOINTS - -aptly defaults to publish to a single publish directory under `rootDir`/public. For -a more advanced publishing strategy, you can define one or more filesystem endpoints in the -`FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name -and the following associated settings: - - * `rootDir`: - The publish directory, e.g., `/opt/srv/aptly_public`. - * `linkMethod`: - This is one of `hardlink`, `symlink` or `copy`. It specifies how aptly links the - files from the internal pool to the published directory. - If not specified, empty or wrong, this defaults to `hardlink`. - * `verifyMethod`: - This is used only when setting the `linkMethod` to `copy`. Possible values are - `md5` and `size`. It specifies how aptly compares existing links from the - internal pool to the published directory. The `size` method compares only the - file sizes, whereas the `md5` method calculates the md5 checksum of the found - file and compares it to the desired one. - If not specified, empty or wrong, this defaults to `md5`. - -In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name` -with `endpoint-name` as the name given in the aptly configuration file. For example: - - `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily` - -## S3 PUBLISHING ENDPOINTS - -aptly could be configured to publish repository directly to Amazon S3 (or S3-compatible -cloud storage). First, publishing -endpoints should be described in aptly configuration file. Each endpoint has name -and associated settings: - - * `region`: - Amazon region for S3 bucket (e.g. `us-east-1`) - * `bucket`: - bucket name - * `endpoint`: - (optional) when using S3-compatible cloud storage, specify hostname of service endpoint here, - region is ignored if endpoint is set (set region to some human-readable name) - (should be left blank for real Amazon S3) - * `prefix`: - (optional) do publishing under specified prefix in the bucket, defaults to - no prefix (bucket root) - * `acl`: - (optional) assign ACL to published files (one of the canned ACLs in Amazon - terminology). Useful values: `private` (default), `public-read` (public - repository) or `none` (don't set ACL). Public repositories could be consumed by `apt` using - HTTP endpoint (Amazon bucket should be configured for "website hosting"), - for private repositories special apt S3 transport is required. - * `awsAccessKeyID`, `awsSecretAccessKey`: - (optional) Amazon credentials to access S3 bucket. If not supplied, - environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` - are used. - * `storageClass`: - (optional) Amazon S3 storage class, defaults to `STANDARD`. Other values - available: `REDUCED_REDUNDANCY` (lower price, lower redundancy) - * `encryptionMethod`: - (optional) server-side encryption method, defaults to none. Currently - the only available encryption method is `AES256` - * `plusWorkaround`: - (optional) workaround misbehavior in apt and Amazon S3 - for files with `+` in filename by - creating two copies of package files with `+` in filename: one original - and another one with spaces instead of plus signs - With `plusWorkaround` enabled, package files with plus sign - would be stored twice. aptly might not cleanup files with spaces when published - repository is dropped or updated (switched) to new version of repository (snapshot) - * `disableMultiDel`: - (optional) for S3-compatible cloud storages which do not support `MultiDel` S3 API, - enable this setting (file deletion would be slower with this setting enabled) - * `forceSigV2`: - (optional) disable Signature V4 support, useful with non-AWS S3-compatible object stores - which do not support SigV4, shouldn't be enabled for AWS - * `debug`: - (optional) enables detailed request/response dump for each S3 operation - -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:` - -## OPENSTACK SWIFT PUBLISHING ENDPOINTS - -aptly could be configured to publish repository directly to OpenStack Swift. First, -publishing endpoints should be described in aptly configuration file. Each endpoint -has name and associated settings: - - * `container`: - container name - * `prefix`: - (optional) do publishing under specified prefix in the container, defaults to - no prefix (container root) - * `osname`, `password`: - (optional) OpenStack credentials to access Keystone. If not supplied, - environment variables `OS_USERNAME` and `OS_PASSWORD` are used. - * `tenant`, `tenantid`: - (optional) OpenStack tenant name and id (in order to use v2 authentication). - * `authurl`: - (optional) the full url of Keystone server (including port, and version). - example `http://identity.example.com:5000/v2.0` - -In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before -publishing prefix on the command line, e.g.: - - `aptly publish snapshot jessie-main swift:test:` ## PACKAGE QUERY diff --git a/pgp/gnupg.go b/pgp/gnupg.go index 99d06621..63076c3e 100644 --- a/pgp/gnupg.go +++ b/pgp/gnupg.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -164,8 +163,8 @@ func NewGpgVerifier(finder GPGFinder) *GpgVerifier { } // InitKeyring verifies that gpg is installed and some keys are trusted -func (g *GpgVerifier) InitKeyring() error { - if len(g.keyRings) == 0 { +func (g *GpgVerifier) InitKeyring(verbose bool) error { + if len(g.keyRings) == 0 && verbose { // using default keyring output, err := exec.Command(g.gpg, "--no-default-keyring", "--no-auto-check-trustdb", "--keyring", "trustedkeys.gpg", "--list-keys").Output() if err == nil && len(output) == 0 { @@ -200,7 +199,7 @@ func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (* args = append([]string{"--status-fd", "3"}, args...) cmd := exec.Command(g.gpgv, args...) - tempf, err := ioutil.TempFile("", "aptly-gpg-status") + tempf, err := os.CreateTemp("", "aptly-gpg-status") if err != nil { return nil, err } @@ -278,7 +277,7 @@ func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (* func (g *GpgVerifier) VerifyDetachedSignature(signature, cleartext io.Reader, showKeyTip bool) error { args := g.argsKeyrings() - sigf, err := ioutil.TempFile("", "aptly-gpg") + sigf, err := os.CreateTemp("", "aptly-gpg") if err != nil { return err } @@ -290,7 +289,7 @@ func (g *GpgVerifier) VerifyDetachedSignature(signature, cleartext io.Reader, sh return err } - clearf, err := ioutil.TempFile("", "aptly-gpg") + clearf, err := os.CreateTemp("", "aptly-gpg") if err != nil { return err } @@ -323,7 +322,7 @@ func (g *GpgVerifier) IsClearSigned(clearsigned io.Reader) (bool, error) { func (g *GpgVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyTip bool) (*KeyInfo, error) { args := g.argsKeyrings() - clearf, err := ioutil.TempFile("", "aptly-gpg") + clearf, err := os.CreateTemp("", "aptly-gpg") if err != nil { return nil, err } @@ -341,7 +340,7 @@ func (g *GpgVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyTip bool) // ExtractClearsigned extracts cleartext from clearsigned file WITHOUT signature verification func (g *GpgVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, err error) { - clearf, err := ioutil.TempFile("", "aptly-gpg") + clearf, err := os.CreateTemp("", "aptly-gpg") if err != nil { return } @@ -353,7 +352,7 @@ func (g *GpgVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, return } - text, err = ioutil.TempFile("", "aptly-gpg") + text, err = os.CreateTemp("", "aptly-gpg") if err != nil { return } diff --git a/pgp/gnupg_test.go b/pgp/gnupg_test.go index e1a30a0b..d17dbe20 100644 --- a/pgp/gnupg_test.go +++ b/pgp/gnupg_test.go @@ -94,7 +94,7 @@ func (s *Gnupg1VerifierSuite) SetUpTest(c *C) { s.verifier = NewGpgVerifier(finder) s.verifier.AddKeyring("./trusted.gpg") - c.Assert(s.verifier.InitKeyring(), IsNil) + c.Assert(s.verifier.InitKeyring(false), IsNil) } type Gnupg1SignerSuite struct { @@ -110,8 +110,8 @@ func (s *Gnupg1SignerSuite) SetUpTest(c *C) { c.Skip(err.Error()) } - s.keyringNoPassphrase = [2]string{"keyrings/aptly.pub", "keyrings/aptly.sec"} - s.keyringPassphrase = [2]string{"keyrings/aptly_passphrase.pub", "keyrings/aptly_passphrase.sec"} + s.keyringNoPassphrase = [2]string{"../system/files/aptly.pub", "../system/files/aptly.sec"} + s.keyringPassphrase = [2]string{"../system/files/aptly_passphrase.pub", "../system/files/aptly_passphrase.sec"} s.passphraseKey = "F30E8CB9CDDE2AF8" s.noPassphraseKey = "21DBB89C16DB3E6D" @@ -119,10 +119,10 @@ func (s *Gnupg1SignerSuite) SetUpTest(c *C) { s.signer.SetBatch(true) s.verifier = &GoVerifier{} - s.verifier.AddKeyring("./keyrings/aptly.pub") - s.verifier.AddKeyring("./keyrings/aptly_passphrase.pub") + s.verifier.AddKeyring("../system/files/aptly.pub") + s.verifier.AddKeyring("../system/files/aptly_passphrase.pub") - c.Assert(s.verifier.InitKeyring(), IsNil) + c.Assert(s.verifier.InitKeyring(false), IsNil) s.SignerSuite.SetUpTest(c) } @@ -143,7 +143,7 @@ func (s *Gnupg2VerifierSuite) SetUpTest(c *C) { s.verifier = NewGpgVerifier(finder) s.verifier.AddKeyring("./trusted.gpg") - c.Assert(s.verifier.InitKeyring(), IsNil) + c.Assert(s.verifier.InitKeyring(false), IsNil) } type Gnupg2SignerSuite struct { @@ -183,7 +183,7 @@ func (s *Gnupg2SignerSuite) SetUpTest(c *C) { args = append(args, "--pinentry-mode", "loopback") } } - args = append(args, "keyrings/aptly2"+item.suffix+".sec.armor") + args = append(args, "../system/files/aptly2"+item.suffix+".sec.armor") output, err := exec.Command(gpg, args...).CombinedOutput() c.Log(string(output)) @@ -193,14 +193,14 @@ func (s *Gnupg2SignerSuite) SetUpTest(c *C) { // import public keys into gpg2 // we can't use pre-built keyrings as gpg 2.0.x and 2.1+ have different keyring formats for _, suffix := range []string{"", "_passphrase"} { - output, err := exec.Command(gpg, "--no-default-keyring", "--batch", "--keyring", "./keyrings/aptly2"+suffix+".gpg", - "--import", "keyrings/aptly2"+suffix+".pub.armor").CombinedOutput() + output, err := exec.Command(gpg, "--no-default-keyring", "--batch", "--keyring", "../system/files/aptly2"+suffix+".gpg", + "--import", "../system/files/aptly2"+suffix+".pub.armor").CombinedOutput() c.Log(string(output)) c.Check(err, IsNil) } - s.keyringNoPassphrase = [2]string{"./keyrings/aptly2.gpg", ""} - s.keyringPassphrase = [2]string{"./keyrings/aptly2_passphrase.gpg", ""} + s.keyringNoPassphrase = [2]string{"../system/files/aptly2.gpg", ""} + s.keyringPassphrase = [2]string{"../system/files/aptly2_passphrase.gpg", ""} s.noPassphraseKey = "751DF85C2B220D45" s.passphraseKey = "6656CD181E92D2D5" @@ -208,9 +208,9 @@ func (s *Gnupg2SignerSuite) SetUpTest(c *C) { s.signer.SetBatch(true) s.verifier = &GoVerifier{} - s.verifier.AddKeyring("./keyrings/aptly2_trusted.pub") + s.verifier.AddKeyring("../system/files/aptly2_trusted.pub") - c.Assert(s.verifier.InitKeyring(), IsNil) + c.Assert(s.verifier.InitKeyring(false), IsNil) s.skipDefaultKey = true @@ -220,6 +220,6 @@ func (s *Gnupg2SignerSuite) SetUpTest(c *C) { func (s *Gnupg2SignerSuite) TearDownTest(c *C) { s.SignerSuite.TearDownTest(c) - os.Remove("./keyrings/aptly2.gpg") - os.Remove("./keyrings/aptly2_passphrase.gpg") + os.Remove("../system/files/aptly2.gpg") + os.Remove("../system/files/aptly2_passphrase.gpg") } diff --git a/pgp/internal.go b/pgp/internal.go index add64267..9796295c 100644 --- a/pgp/internal.go +++ b/pgp/internal.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "path/filepath" "sort" @@ -14,12 +13,10 @@ import ( "github.com/pkg/errors" - // TODO: replace crypto/openpgp since it is deprecated - // https://github.com/golang/go/issues/44226 - "golang.org/x/crypto/openpgp" //nolint:staticcheck - "golang.org/x/crypto/openpgp/clearsign" //nolint:staticcheck - openpgp_errors "golang.org/x/crypto/openpgp/errors" //nolint:staticcheck - "golang.org/x/crypto/openpgp/packet" //nolint:staticcheck + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/clearsign" + openpgp_errors "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" "golang.org/x/term" ) @@ -47,7 +44,8 @@ type GoSigner struct { signerConfig *packet.Config } -// SetBatch controls whether we allowed to interact with user +// SetBatch controls whether we allowed to interact with user, for example +// for getting the passphrase from stdin. func (g *GoSigner) SetBatch(batch bool) { g.batch = batch } @@ -83,7 +81,7 @@ func (g *GoSigner) Init() error { } defer passF.Close() - contents, err := ioutil.ReadAll(passF) + contents, err := io.ReadAll(passF) if err != nil { return errors.Wrap(err, "error reading passphrase file") } @@ -285,7 +283,7 @@ type GoVerifier struct { } // InitKeyring verifies that gpg is installed and some keys are trusted -func (g *GoVerifier) InitKeyring() error { +func (g *GoVerifier) InitKeyring(verbose bool) error { var err error if len(g.keyRingFiles) == 0 { @@ -306,7 +304,7 @@ func (g *GoVerifier) InitKeyring() error { } } - if len(g.trustedKeyring) == 0 { + if len(g.trustedKeyring) == 0 && verbose { fmt.Printf("\nLooks like your keyring with trusted keys is empty. You might consider importing some keys.\n") if len(g.keyRingFiles) == 0 { // using default keyring @@ -400,7 +398,7 @@ func (g *GoVerifier) VerifyDetachedSignature(signature, cleartext io.Reader, sho // IsClearSigned returns true if file contains signature func (g *GoVerifier) IsClearSigned(clearsigned io.Reader) (bool, error) { - signedBuffer, err := ioutil.ReadAll(clearsigned) + signedBuffer, err := io.ReadAll(clearsigned) if err != nil { return false, errors.Wrap(err, "failed to read clearsigned data") } @@ -412,7 +410,7 @@ func (g *GoVerifier) IsClearSigned(clearsigned io.Reader) (bool, error) { // VerifyClearsigned verifies clearsigned file using gpgv func (g *GoVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyTip bool) (*KeyInfo, error) { - signedBuffer, err := ioutil.ReadAll(clearsigned) + signedBuffer, err := io.ReadAll(clearsigned) if err != nil { return nil, errors.Wrap(err, "failed to read clearsigned data") } @@ -451,7 +449,7 @@ func (g *GoVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyTip bool) ( // ExtractClearsigned extracts cleartext from clearsigned file WITHOUT signature verification func (g *GoVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, err error) { var signedBuffer []byte - signedBuffer, err = ioutil.ReadAll(clearsigned) + signedBuffer, err = io.ReadAll(clearsigned) if err != nil { return nil, errors.Wrap(err, "failed to read clearsigned data") } @@ -461,7 +459,7 @@ func (g *GoVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, e return nil, errors.New("no clearsigned data found") } - text, err = ioutil.TempFile("", "aptly-gpg") + text, err = os.CreateTemp("", "aptly-gpg") if err != nil { return } diff --git a/pgp/internal_test.go b/pgp/internal_test.go index 1780e73d..8601179f 100644 --- a/pgp/internal_test.go +++ b/pgp/internal_test.go @@ -14,7 +14,7 @@ func (s *GoVerifierSuite) SetUpTest(c *C) { s.verifier = &GoVerifier{} s.verifier.AddKeyring("./trusted.gpg") - c.Assert(s.verifier.InitKeyring(), IsNil) + c.Assert(s.verifier.InitKeyring(false), IsNil) } type GoSignerSuite struct { @@ -24,8 +24,8 @@ type GoSignerSuite struct { var _ = Suite(&GoSignerSuite{}) func (s *GoSignerSuite) SetUpTest(c *C) { - s.keyringNoPassphrase = [2]string{"keyrings/aptly.pub", "keyrings/aptly.sec"} - s.keyringPassphrase = [2]string{"keyrings/aptly_passphrase.pub", "keyrings/aptly_passphrase.sec"} + s.keyringNoPassphrase = [2]string{"../system/files/aptly.pub", "../system/files/aptly.sec"} + s.keyringPassphrase = [2]string{"../system/files/aptly_passphrase.pub", "../system/files/aptly_passphrase.sec"} s.passphraseKey = "F30E8CB9CDDE2AF8" s.noPassphraseKey = "21DBB89C16DB3E6D" @@ -33,10 +33,10 @@ func (s *GoSignerSuite) SetUpTest(c *C) { s.signer.SetBatch(true) s.verifier = &GoVerifier{} - s.verifier.AddKeyring("./keyrings/aptly.pub") - s.verifier.AddKeyring("./keyrings/aptly_passphrase.pub") + s.verifier.AddKeyring("../system/files/aptly.pub") + s.verifier.AddKeyring("../system/files/aptly_passphrase.pub") - c.Assert(s.verifier.InitKeyring(), IsNil) + c.Assert(s.verifier.InitKeyring(false), IsNil) s.SignerSuite.SetUpTest(c) } diff --git a/pgp/keyrings/aptly.pub b/pgp/keyrings/aptly.pub deleted file mode 100644 index 08758e43..00000000 Binary files a/pgp/keyrings/aptly.pub and /dev/null differ diff --git a/pgp/keyrings/aptly.sec b/pgp/keyrings/aptly.sec deleted file mode 100644 index f90e1c91..00000000 Binary files a/pgp/keyrings/aptly.sec and /dev/null differ diff --git a/pgp/keyrings/aptly_passphrase.pub b/pgp/keyrings/aptly_passphrase.pub deleted file mode 100644 index ec24aa32..00000000 Binary files a/pgp/keyrings/aptly_passphrase.pub and /dev/null differ diff --git a/pgp/keyrings/aptly_passphrase.sec b/pgp/keyrings/aptly_passphrase.sec deleted file mode 100644 index 2ffe24ef..00000000 Binary files a/pgp/keyrings/aptly_passphrase.sec and /dev/null differ diff --git a/pgp/openpgp.go b/pgp/openpgp.go index 8577fc00..335a9ad9 100644 --- a/pgp/openpgp.go +++ b/pgp/openpgp.go @@ -11,12 +11,10 @@ import ( "strconv" "time" - // TODO: replace crypto/openpgp since it is deprecated - // https://github.com/golang/go/issues/44226 - "golang.org/x/crypto/openpgp" //nolint:staticcheck - "golang.org/x/crypto/openpgp/armor" //nolint:staticcheck - "golang.org/x/crypto/openpgp/errors" //nolint:staticcheck - "golang.org/x/crypto/openpgp/packet" //nolint:staticcheck + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" ) // hashForSignature returns a pair of hashes that can be used to verify a @@ -94,12 +92,6 @@ func checkDetachedSignature(keyring openpgp.KeyRing, signed, signature io.Reader sigType = sig.SigType creationTime = sig.CreationTime pubKeyAlgo = sig.PubKeyAlgo - case *packet.SignatureV3: - issuerKeyID = sig.IssuerKeyId - hashFunc = sig.Hash - sigType = sig.SigType - creationTime = sig.CreationTime - pubKeyAlgo = sig.PubKeyAlgo default: return nil, 0, errors.StructuralError("non signature packet found") } @@ -129,8 +121,6 @@ func checkDetachedSignature(keyring openpgp.KeyRing, signed, signature io.Reader switch sig := p.(type) { case *packet.Signature: err = key.PublicKey.VerifySignature(h, sig) - case *packet.SignatureV3: - err = key.PublicKey.VerifySignatureV3(h, sig) default: panic("unreachable") } @@ -192,6 +182,8 @@ func pubkeyAlgorithmName(algorithm packet.PublicKeyAlgorithm) string { return "EDCH" case packet.PubKeyAlgoECDSA: return "ECDSA" + case packet.PubKeyAlgoEdDSA: + return "EdDSA" } return "unknown" diff --git a/pgp/pgp.go b/pgp/pgp.go index 37ef497c..228dbaaa 100644 --- a/pgp/pgp.go +++ b/pgp/pgp.go @@ -51,7 +51,7 @@ type Signer interface { // Verifier interface describes signature verification factility type Verifier interface { - InitKeyring() error + InitKeyring(verbose bool) error AddKeyring(keyring string) VerifyDetachedSignature(signature, cleartext io.Reader, showKeyTip bool) error IsClearSigned(clearsigned io.Reader) (bool, error) diff --git a/s3/public.go b/s3/public.go index b44c7fe6..47623a00 100644 --- a/s3/public.go +++ b/s3/public.go @@ -1,6 +1,7 @@ package s3 import ( + "context" "fmt" "io" "os" @@ -9,31 +10,52 @@ import ( "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/utils" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/corehandlers" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/s3" + + "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/logging" "github.com/pkg/errors" - awsauth "github.com/smira/go-aws-auth" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) const errCodeNotFound = "NotFound" +type logger struct{} + +func (l *logger) Logf(classification logging.Classification, format string, v ...interface{}) { + var e *zerolog.Event + switch classification { + case logging.Debug: + e = log.Logger.Debug() + case logging.Warn: + e = log.Logger.Warn() + default: + e = log.Logger.Error() + } + e.Msgf(format, v...) +} + // PublishedStorage abstract file system with published files (actually hosted on S3) type PublishedStorage struct { - s3 *s3.S3 + s3 *s3.Client config *aws.Config bucket string - acl string + acl types.ObjectCannedACL prefix string - storageClass string - encryptionMethod string + storageClass types.StorageClass + encryptionMethod types.ServerSideEncryption plusWorkaround bool disableMultiDel bool pathCache map[string]string + + // True if the bucket encrypts objects by default. + encryptByDefault bool } // Check interface @@ -44,90 +66,96 @@ var ( // NewPublishedStorageRaw creates published storage from raw aws credentials func NewPublishedStorageRaw( bucket, defaultACL, prefix, storageClass, encryptionMethod string, - plusWorkaround, disabledMultiDel bool, - config *aws.Config, + plusWorkaround, disabledMultiDel, forceVirtualHostedStyle bool, + config *aws.Config, endpoint string, ) (*PublishedStorage, error) { - if defaultACL == "" { - defaultACL = "private" + var acl types.ObjectCannedACL + if defaultACL == "" || defaultACL == "private" { + acl = types.ObjectCannedACLPrivate + } else if defaultACL == "public-read" { + acl = types.ObjectCannedACLPublicRead } else if defaultACL == "none" { - defaultACL = "" + acl = "" } - if storageClass == "STANDARD" { + if storageClass == string(types.StorageClassStandard) { storageClass = "" } - sess, err := session.NewSession(config) - if err != nil { - return nil, err + var baseEndpoint *string + if endpoint != "" { + baseEndpoint = aws.String(endpoint) } result := &PublishedStorage{ - s3: s3.New(sess), + s3: s3.NewFromConfig(*config, func(o *s3.Options) { + o.UsePathStyle = !forceVirtualHostedStyle + o.HTTPSignerV4 = v4.NewSigner() + o.BaseEndpoint = baseEndpoint + }), bucket: bucket, config: config, - acl: defaultACL, + acl: acl, prefix: prefix, - storageClass: storageClass, - encryptionMethod: encryptionMethod, + storageClass: types.StorageClass(storageClass), + encryptionMethod: types.ServerSideEncryption(encryptionMethod), plusWorkaround: plusWorkaround, disableMultiDel: disabledMultiDel, } + result.setKMSFlag() + return result, nil } +func (storage *PublishedStorage) setKMSFlag() { + params := &s3.GetBucketEncryptionInput{ + Bucket: aws.String(storage.bucket), + } + output, err := storage.s3.GetBucketEncryption(context.TODO(), params) + if err != nil { + return + } + + if len(output.ServerSideEncryptionConfiguration.Rules) > 0 && + output.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm == "aws:kms" { + storage.encryptByDefault = true + } +} + // NewPublishedStorage creates new instance of PublishedStorage with specified S3 access // keys, region and bucket name -func NewPublishedStorage(accessKey, secretKey, sessionToken, region, endpoint, bucket, defaultACL, prefix, - storageClass, encryptionMethod string, plusWorkaround, disableMultiDel, forceSigV2, debug bool) (*PublishedStorage, error) { - - config := &aws.Config{ - Region: aws.String(region), - } - - if endpoint != "" { - config = config.WithEndpoint(endpoint).WithS3ForcePathStyle(true) - } +func NewPublishedStorage( + accessKey, secretKey, sessionToken, region, endpoint, bucket, defaultACL, prefix, storageClass, encryptionMethod string, + plusWorkaround, disableMultiDel, _, forceVirtualHostedStyle, debug bool) (*PublishedStorage, error) { + opts := []func(*config.LoadOptions) error{config.WithRegion(region)} if accessKey != "" { - config.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, sessionToken) + opts = append(opts, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, sessionToken))) } if debug { - config = config.WithLogLevel(aws.LogDebug) + opts = append(opts, config.WithLogger(&logger{})) + } + + config, err := config.LoadDefaultConfig(context.TODO(), opts...) + if err != nil { + return nil, err } result, err := NewPublishedStorageRaw(bucket, defaultACL, prefix, storageClass, - encryptionMethod, plusWorkaround, disableMultiDel, config) - - if err == nil && forceSigV2 { - creds := []awsauth.Credentials{} - - if accessKey != "" { - creds = append(creds, awsauth.Credentials{ - AccessKeyID: accessKey, - SecretAccessKey: secretKey, - }) - } - - result.s3.Handlers.Sign.Clear() - result.s3.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) - result.s3.Handlers.Sign.PushBack(func(req *request.Request) { - awsauth.SignS3(req.HTTPRequest, creds...) - }) - } + encryptionMethod, plusWorkaround, disableMultiDel, forceVirtualHostedStyle, &config, endpoint) return result, err } // String func (storage *PublishedStorage) String() string { - return fmt.Sprintf("S3: %s:%s/%s", *storage.config.Region, storage.bucket, storage.prefix) + return fmt.Sprintf("S3: %s:%s/%s", storage.config.Region, storage.bucket, storage.prefix) } // MkDir creates directory recursively under public path -func (storage *PublishedStorage) MkDir(path string) error { +func (storage *PublishedStorage) MkDir(_ string) error { // no op for S3 return nil } @@ -158,12 +186,12 @@ func (storage *PublishedStorage) getMD5(path string) (string, error) { Bucket: aws.String(storage.bucket), Key: aws.String(filepath.Join(storage.prefix, path)), } - output, err := storage.s3.HeadObject(params) + output, err := storage.s3.HeadObject(context.TODO(), params) if err != nil { return "", err } - return aws.StringValue(output.Metadata["Md5"]), nil + return output.Metadata["Md5"], nil } // putFile uploads file-like object to @@ -173,21 +201,21 @@ func (storage *PublishedStorage) putFile(path string, source io.ReadSeeker, sour Bucket: aws.String(storage.bucket), Key: aws.String(filepath.Join(storage.prefix, path)), Body: source, - ACL: aws.String(storage.acl), + ACL: storage.acl, } if storage.storageClass != "" { - params.StorageClass = aws.String(storage.storageClass) + params.StorageClass = types.StorageClass(storage.storageClass) } if storage.encryptionMethod != "" { - params.ServerSideEncryption = aws.String(storage.encryptionMethod) + params.ServerSideEncryption = types.ServerSideEncryption(storage.encryptionMethod) } if sourceMD5 != "" { - params.Metadata = map[string]*string{ - "Md5": aws.String(sourceMD5), + params.Metadata = map[string]string{ + "Md5": sourceMD5, } } - _, err := storage.s3.PutObject(params) + _, err := storage.s3.PutObject(context.TODO(), params) if err != nil { return err } @@ -209,13 +237,12 @@ func (storage *PublishedStorage) Remove(path string) error { Bucket: aws.String(storage.bucket), Key: aws.String(filepath.Join(storage.prefix, path)), } - _, err := storage.s3.DeleteObject(params) - if err != nil { - if aerr, ok := err.(awserr.Error); ok { - if aerr.Code() == s3.ErrCodeNoSuchBucket { - // ignore 'no such bucket' errors on removal - return nil - } + + if _, err := storage.s3.DeleteObject(context.TODO(), params); err != nil { + var notFoundErr *smithy.GenericAPIError + if errors.As(err, ¬FoundErr) && notFoundErr.Code == "NoSuchBucket" { + // ignore 'no such bucket' errors on removal + return nil } return errors.Wrap(err, fmt.Sprintf("error deleting %s from %s", path, storage)) } @@ -224,20 +251,21 @@ func (storage *PublishedStorage) Remove(path string) error { // try to remove workaround version, but don't care about result _ = storage.Remove(strings.Replace(path, "+", " ", -1)) } + + delete(storage.pathCache, path) + return nil } // RemoveDirs removes directory structure under public path -func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error { +func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error { const page = 1000 filelist, _, err := storage.internalFilelist(path, false) if err != nil { - if aerr, ok := errors.Cause(err).(awserr.Error); ok { - if aerr.Code() == s3.ErrCodeNoSuchBucket { - // ignore 'no such bucket' errors on removal - return nil - } + if errors.Is(err, &types.NoSuchBucket{}) { + // ignore 'no such bucket' errors on removal + return nil } return err } @@ -248,10 +276,11 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress Bucket: aws.String(storage.bucket), Key: aws.String(filepath.Join(storage.prefix, path, filelist[i])), } - _, err := storage.s3.DeleteObject(params) + _, err := storage.s3.DeleteObject(context.TODO(), params) if err != nil { return fmt.Errorf("error deleting path %s from %s: %s", filelist[i], storage, err) } + delete(storage.pathCache, filepath.Join(path, filelist[i])) } } else { numParts := (len(filelist) + page - 1) / page @@ -263,26 +292,30 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress } else { part = filelist[i*page : (i+1)*page] } - paths := make([]*s3.ObjectIdentifier, len(part)) + paths := make([]types.ObjectIdentifier, len(part)) for i := range part { - paths[i] = &s3.ObjectIdentifier{ + paths[i] = types.ObjectIdentifier{ Key: aws.String(filepath.Join(storage.prefix, path, part[i])), } } + quiet := true params := &s3.DeleteObjectsInput{ Bucket: aws.String(storage.bucket), - Delete: &s3.Delete{ + Delete: &types.Delete{ Objects: paths, - Quiet: aws.Bool(true), + Quiet: &quiet, }, } - _, err := storage.s3.DeleteObjects(params) + _, err := storage.s3.DeleteObjects(context.TODO(), params) if err != nil { return fmt.Errorf("error deleting multiple paths from %s: %s", storage, err) } + for i := range part { + delete(storage.pathCache, filepath.Join(path, part[i])) + } } } @@ -291,19 +324,21 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress // LinkFromPool links package file from pool to dist's pool location // -// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) +// publishedPrefix is desired prefix for the location in the pool. +// publishedRelPath is desired location in pool (like pool/component/liba/libav/) // sourcePool is instance of aptly.PackagePool // sourcePath is filepath to package file in package pool // // LinkFromPool returns relative path for the published file to be included in package index -func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool, +func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error { + publishedDirectory := filepath.Join(publishedPrefix, publishedRelPath) relPath := filepath.Join(publishedDirectory, fileName) poolPath := filepath.Join(storage.prefix, relPath) if storage.pathCache == nil { - paths, md5s, err := storage.internalFilelist("", true) + paths, md5s, err := storage.internalFilelist(filepath.Join(storage.prefix, publishedPrefix, "pool"), true) if err != nil { return errors.Wrap(err, "error caching paths under prefix") } @@ -319,7 +354,11 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin sourceMD5 := sourceChecksums.MD5 if exists { - if len(destinationMD5) != 32 { + if sourceMD5 == "" { + return fmt.Errorf("unable to compare object, MD5 checksum missing") + } + + if len(destinationMD5) != 32 || storage.encryptByDefault { // doesn’t look like a valid MD5, // attempt to fetch one from the metadata var err error @@ -330,17 +369,13 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin } storage.pathCache[relPath] = destinationMD5 } - if sourceMD5 == "" { - return fmt.Errorf("unable to compare object, MD5 checksum missing") - } if destinationMD5 == sourceMD5 { return nil } - if !force && destinationMD5 != sourceMD5 { + if !force { return fmt.Errorf("error putting file to %s: file already exists and is different: %s", poolPath, storage) - } } @@ -374,14 +409,22 @@ func (storage *PublishedStorage) internalFilelist(prefix string, hidePlusWorkaro prefix += "/" } - params := &s3.ListObjectsInput{ + maxKeys := int32(1000) + params := &s3.ListObjectsV2Input{ Bucket: aws.String(storage.bucket), Prefix: aws.String(prefix), - MaxKeys: aws.Int64(1000), + MaxKeys: &maxKeys, } - err = storage.s3.ListObjectsPages(params, func(contents *s3.ListObjectsOutput, lastPage bool) bool { - for _, key := range contents.Contents { + p := s3.NewListObjectsV2Paginator(storage.s3, params) + for i := 1; p.HasMorePages(); i++ { + page, err := p.NextPage(context.TODO()) + if err != nil { + return nil, nil, fmt.Errorf("failed to get page %d: %w", i, err) + } + + // Log the objects found + for _, key := range page.Contents { if storage.plusWorkaround && hidePlusWorkaround && strings.Contains(*key.Key, " ") { // if we use plusWorkaround, we want to hide those duplicates /// from listing @@ -395,9 +438,7 @@ func (storage *PublishedStorage) internalFilelist(prefix string, hidePlusWorkaro } md5s = append(md5s, strings.Replace(*key.ETag, "\"", "", -1)) } - - return true - }) + } if err != nil { return nil, nil, errors.WithMessagef(err, "error listing under prefix %s in %s: %s", prefix, storage, err) @@ -414,17 +455,17 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error { Bucket: aws.String(storage.bucket), CopySource: aws.String(source), Key: aws.String(filepath.Join(storage.prefix, newName)), - ACL: aws.String(storage.acl), + ACL: storage.acl, } if storage.storageClass != "" { - params.StorageClass = aws.String(storage.storageClass) + params.StorageClass = storage.storageClass } if storage.encryptionMethod != "" { - params.ServerSideEncryption = aws.String(storage.encryptionMethod) + params.ServerSideEncryption = storage.encryptionMethod } - _, err := storage.s3.CopyObject(params) + _, err := storage.s3.CopyObject(context.TODO(), params) if err != nil { return fmt.Errorf("error copying %s -> %s in %s: %s", oldName, newName, storage, err) } @@ -439,21 +480,21 @@ func (storage *PublishedStorage) SymLink(src string, dst string) error { Bucket: aws.String(storage.bucket), CopySource: aws.String(filepath.Join(storage.bucket, storage.prefix, src)), Key: aws.String(filepath.Join(storage.prefix, dst)), - ACL: aws.String(storage.acl), - Metadata: map[string]*string{ - "SymLink": aws.String(src), + ACL: types.ObjectCannedACL(storage.acl), + Metadata: map[string]string{ + "SymLink": src, }, - MetadataDirective: aws.String("REPLACE"), + MetadataDirective: types.MetadataDirective("REPLACE"), } if storage.storageClass != "" { - params.StorageClass = aws.String(storage.storageClass) + params.StorageClass = types.StorageClass(storage.storageClass) } if storage.encryptionMethod != "" { - params.ServerSideEncryption = aws.String(storage.encryptionMethod) + params.ServerSideEncryption = types.ServerSideEncryption(storage.encryptionMethod) } - _, err := storage.s3.CopyObject(params) + _, err := storage.s3.CopyObject(context.TODO(), params) if err != nil { return fmt.Errorf("error symlinking %s -> %s in %s: %s", src, dst, storage, err) } @@ -472,13 +513,24 @@ func (storage *PublishedStorage) FileExists(path string) (bool, error) { Bucket: aws.String(storage.bucket), Key: aws.String(filepath.Join(storage.prefix, path)), } - _, err := storage.s3.HeadObject(params) + _, err := storage.s3.HeadObject(context.TODO(), params) if err != nil { - aerr, ok := err.(awserr.Error) - if ok && aerr.Code() == errCodeNotFound { + var notFoundErr *types.NotFound + if errors.As(err, ¬FoundErr) { return false, nil } + // falback in case the above condidition fails + var opErr *smithy.OperationError + if errors.As(err, &opErr) { + var ae smithy.APIError + if errors.As(err, &ae) { + if ae.ErrorCode() == "NotFound" { + return false, nil + } + } + } + return false, err } @@ -492,10 +544,10 @@ func (storage *PublishedStorage) ReadLink(path string) (string, error) { Bucket: aws.String(storage.bucket), Key: aws.String(filepath.Join(storage.prefix, path)), } - output, err := storage.s3.HeadObject(params) + output, err := storage.s3.HeadObject(context.TODO(), params) if err != nil { return "", err } - return aws.StringValue(output.Metadata["SymLink"]), nil + return output.Metadata["SymLink"], nil } diff --git a/s3/public_test.go b/s3/public_test.go index 72fe21ac..83605e3e 100644 --- a/s3/public_test.go +++ b/s3/public_test.go @@ -2,14 +2,18 @@ package s3 import ( "bytes" + "context" "io/ioutil" "os" "path/filepath" + "sort" + "strings" . "gopkg.in/check.v1" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aptly-dev/aptly/files" "github.com/aptly-dev/aptly/utils" @@ -29,14 +33,18 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { c.Assert(err, IsNil) c.Assert(s.srv, NotNil) - s.storage, err = NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "test", "", "", "", "", false, true, false, false) + s.storage, err = NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "test", "", "", "", "", false, true, false, false, false) c.Assert(err, IsNil) - s.prefixedStorage, err = NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "test", "", "lala", "", "", false, true, false, false) + s.prefixedStorage, err = NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "test", "", "lala", "", "", false, true, false, false, false) c.Assert(err, IsNil) - s.noSuchBucketStorage, err = NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "no-bucket", "", "", "", "", false, true, false, false) + s.noSuchBucketStorage, err = NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "no-bucket", "", "", "", "", false, true, false, false, false) c.Assert(err, IsNil) - _, err = s.storage.s3.CreateBucket(&s3.CreateBucketInput{Bucket: aws.String("test")}) + _, err = s.storage.s3.CreateBucket(context.TODO(), &s3.CreateBucketInput{ + Bucket: aws.String("test"), + CreateBucketConfiguration: &types.CreateBucketConfiguration{ + LocationConstraint: "test-1", + }}) c.Assert(err, IsNil) } @@ -44,8 +52,19 @@ func (s *PublishedStorageSuite) TearDownTest(c *C) { s.srv.Quit() } +func (s *PublishedStorageSuite) checkGetRequestsEqual(c *C, prefix string, expectedGetRequestUris []string) { + getRequests := make([]string, 0, len(s.srv.Requests)) + for _, r := range s.srv.Requests { + if r.Method == "GET" && strings.HasPrefix(r.RequestURI, prefix) { + getRequests = append(getRequests, r.RequestURI) + } + } + sort.Strings(getRequests) + c.Check(getRequests, DeepEquals, expectedGetRequestUris) +} + func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte { - resp, err := s.storage.s3.GetObject(&s3.GetObjectInput{ + resp, err := s.storage.s3.GetObject(context.TODO(), &s3.GetObjectInput{ Bucket: aws.String(s.storage.bucket), Key: aws.String(path), }) @@ -59,20 +78,20 @@ func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte { } func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) { - _, err := s.storage.s3.HeadObject(&s3.HeadObjectInput{ + _, err := s.storage.s3.HeadObject(context.TODO(), &s3.HeadObjectInput{ Bucket: aws.String(s.storage.bucket), Key: aws.String(path), }) - c.Assert(err, ErrorMatches, ".*\n.*status code: 404.*") + c.Assert(err, ErrorMatches, ".*StatusCode: 404.*") } func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) { - _, err := s.storage.s3.PutObject(&s3.PutObjectInput{ + _, err := s.storage.s3.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String(s.storage.bucket), Key: aws.String(path), Body: bytes.NewReader(data), ContentType: aws.String("binary/octet-stream"), - ACL: aws.String("private"), + ACL: types.ObjectCannedACLPrivate, }) c.Assert(err, IsNil) } @@ -228,7 +247,7 @@ func (s *PublishedStorageSuite) TestRemoveDirsPlusWorkaround(c *C) { func (s *PublishedStorageSuite) TestRemoveDirsNoSuchBucket(c *C) { err := s.noSuchBucketStorage.RemoveDirs("a/b", nil) - c.Check(err, IsNil) + c.Check(err, ErrorMatches, ".*StatusCode: 404.*") } func (s *PublishedStorageSuite) TestRenameFile(c *C) { @@ -264,50 +283,118 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Assert(err, IsNil) // first link from pool - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // duplicate link from pool - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // link from pool with conflict - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false) c.Check(err, ErrorMatches, ".*file already exists and is different.*") c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // link from pool with conflict and force - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true) c.Check(err, IsNil) c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Spam")) // for prefixed storage: // first link from pool - err = s.prefixedStorage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) // 2nd link from pool, providing wrong path for source file // // this test should check that file already exists in S3 and skip upload (which would fail if not skipped) - s.prefixedStorage.pathCache = nil - err = s.prefixedStorage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false) + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "lala/pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents")) // link from pool with nested file name - err = s.storage.LinkFromPool("dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false) + err = s.storage.LinkFromPool("", "dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false) c.Check(err, IsNil) c.Check(s.GetFile(c, "dists/jessie/non-free/installer-i386/current/images/netboot/boot.img.gz"), DeepEquals, []byte("Contents")) } +func (s *PublishedStorageSuite) TestLinkFromPoolCache(c *C) { + root := c.MkDir() + pool := files.NewPackagePool(root, false) + cs := files.NewMockChecksumStorage() + + tmpFile1 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb") + err := ioutil.WriteFile(tmpFile1, []byte("Contents"), 0644) + c.Assert(err, IsNil) + cksum1 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"} + + src1, err := pool.Import(tmpFile1, "mars-invaders_1.03.deb", &cksum1, true, cs) + c.Assert(err, IsNil) + + // Publish two packages at the same publish prefix + err = s.storage.LinkFromPool("", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + err = s.storage.LinkFromPool("", filepath.Join("pool", "b"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + // Check only one listing request was done to the server + s.checkGetRequestsEqual(c, "/test?", []string{"/test?encryption=", "/test?encryption=", "/test?list-type=2&max-keys=1000&prefix=pool%2F"}) + + s.srv.Requests = nil + // Publish two packages at a different prefix + err = s.storage.LinkFromPool("publish-prefix", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + err = s.storage.LinkFromPool("publish-prefix", filepath.Join("pool", "b"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + // Check no listing request was done to the server (pathCache is used) + s.checkGetRequestsEqual(c, "/test?", []string{}) + + s.srv.Requests = nil + // Publish two packages at a prefixed storage + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "b"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + // Check only one listing request was done to the server + s.checkGetRequestsEqual(c, "/test?", []string{ + "/test?list-type=2&max-keys=1000&prefix=lala%2Flala%2Fpool%2F", + }) + + // Publish two packages at a prefixed storage plus a publish prefix. + s.srv.Requests = nil + err = s.prefixedStorage.LinkFromPool("publish-prefix", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + err = s.prefixedStorage.LinkFromPool("publish-prefix", filepath.Join("pool", "b"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + c.Check(err, IsNil) + + // Check no listing request was done to the server (pathCache is used) + s.checkGetRequestsEqual(c, "/test?", []string{}) + + // This step checks that files already exists in S3 and skip upload (which would fail if not skipped). + err = s.prefixedStorage.LinkFromPool("publish-prefix", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, "non-existent-file", cksum1, false) + c.Check(err, IsNil) + err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, "non-existent-file", cksum1, false) + c.Check(err, IsNil) + err = s.storage.LinkFromPool("publish-prefix", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, "non-existent-file", cksum1, false) + c.Check(err, IsNil) + err = s.storage.LinkFromPool("", filepath.Join("pool", "a"), "mars-invaders_1.03.deb", pool, "non-existent-file", cksum1, false) + c.Check(err, IsNil) +} + func (s *PublishedStorageSuite) TestSymLink(c *C) { s.PutFile(c, "a/b", []byte("test")) diff --git a/s3/server_test.go b/s3/server_test.go index 573097ab..08c29040 100644 --- a/s3/server_test.go +++ b/s3/server_test.go @@ -59,6 +59,12 @@ func (c *Config) send409Conflict() bool { return false } +// Request stores the method and URI of an HTTP request. +type Request struct { + Method string + RequestURI string +} + // Server is a fake S3 server for testing purposes. // All of the data for the server is kept in memory. type Server struct { @@ -68,6 +74,8 @@ type Server struct { mu sync.Mutex buckets map[string]*bucket config *Config + // Requests holds a log of all requests received by the server. + Requests []Request } type bucket struct { @@ -140,6 +148,7 @@ func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) { if debug { log.Printf("s3test %q %q", req.Method, req.URL) } + srv.Requests = append(srv.Requests, Request{req.Method, req.RequestURI}) a := &action{ srv: srv, w: w, @@ -330,6 +339,10 @@ type Owner struct { DisplayName string } +type CommonPrefix struct { + Prefix string +} + // The ListResp type holds the results of a List bucket operation. type ListResp struct { Name string @@ -344,7 +357,7 @@ type ListResp struct { // http://goo.gl/YjQTc IsTruncated bool Contents []Key - CommonPrefixes []string `xml:">Prefix"` + CommonPrefixes []CommonPrefix } // The Key type represents an item stored in an S3 bucket. @@ -403,7 +416,7 @@ func (r bucketResource) get(a *action) interface{} { MaxKeys: maxKeys, } - var prefixes []string + var prefixes []CommonPrefix for _, obj := range objs { if !strings.HasPrefix(obj.name, prefix) { continue @@ -413,7 +426,7 @@ func (r bucketResource) get(a *action) interface{} { if delimiter != "" { if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 { name = obj.name[:len(prefix)+i+len(delimiter)] - if prefixes != nil && prefixes[len(prefixes)-1] == name { + if prefixes != nil && prefixes[len(prefixes)-1].Prefix == name { continue } isPrefix = true @@ -427,7 +440,7 @@ func (r bucketResource) get(a *action) interface{} { break } if isPrefix { - prefixes = append(prefixes, name) + prefixes = append(prefixes, CommonPrefix{name}) } else { // Contents contains only keys not found in CommonPrefixes resp.Contents = append(resp.Contents, obj.s3Key()) diff --git a/swift/public.go b/swift/public.go index 0ca4796c..d1f01849 100644 --- a/swift/public.go +++ b/swift/public.go @@ -113,7 +113,7 @@ func (storage *PublishedStorage) String() string { } // MkDir creates directory recursively under public path -func (storage *PublishedStorage) MkDir(path string) error { +func (storage *PublishedStorage) MkDir(_ string) error { // no op for Swift return nil } @@ -155,7 +155,7 @@ func (storage *PublishedStorage) Remove(path string) error { } // RemoveDirs removes directory structure under public path -func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error { +func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error { path = filepath.Join(storage.prefix, path) opts := swift.ObjectsOpts{ Prefix: path, @@ -188,15 +188,16 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress // LinkFromPool links package file from pool to dist's pool location // -// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) +// publishedPrefix is desired prefix for the location in the pool. +// publishedRelPath is desired location in pool (like pool/component/liba/libav/) // sourcePool is instance of aptly.PackagePool // sourcePath is filepath to package file in package pool // // LinkFromPool returns relative path for the published file to be included in package index -func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool, +func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error { - relPath := filepath.Join(publishedDirectory, fileName) + relPath := filepath.Join(publishedPrefix, publishedRelPath, fileName) poolPath := filepath.Join(storage.prefix, relPath) var ( diff --git a/swift/public_test.go b/swift/public_test.go index 31b633e8..620b94a2 100644 --- a/swift/public_test.go +++ b/swift/public_test.go @@ -6,7 +6,6 @@ import ( "math/rand" "os" "path/filepath" - "time" . "gopkg.in/check.v1" @@ -27,8 +26,6 @@ var _ = Suite(&PublishedStorageSuite{}) func (s *PublishedStorageSuite) SetUpTest(c *C) { var err error - rand.Seed(int64(time.Now().Nanosecond())) - s.TestAddress = fmt.Sprintf("localhost:%d", rand.Intn(10000)+20000) s.AuthURL = "http://" + s.TestAddress + "/v1.0" @@ -172,7 +169,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Assert(err, IsNil) // first link from pool - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) data, err := s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") @@ -180,7 +177,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(data, DeepEquals, []byte("Contents")) // duplicate link from pool - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false) c.Check(err, IsNil) data, err = s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") @@ -188,7 +185,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(data, DeepEquals, []byte("Contents")) // link from pool with conflict - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false) c.Check(err, ErrorMatches, ".*file already exists and is different.*") data, err = s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") @@ -196,7 +193,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(data, DeepEquals, []byte("Contents")) // link from pool with conflict and force - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true) + err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true) c.Check(err, IsNil) data, err = s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") @@ -204,7 +201,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { c.Check(data, DeepEquals, []byte("Spam")) // link from pool with nested file name - err = s.storage.LinkFromPool("dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false) + err = s.storage.LinkFromPool("", "dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false) c.Check(err, IsNil) data, err = s.storage.conn.ObjectGetBytes("test", "dists/jessie/non-free/installer-i386/current/images/netboot/boot.img.gz") diff --git a/system/Dockerfile b/system/Dockerfile new file mode 100644 index 00000000..f2435244 --- /dev/null +++ b/system/Dockerfile @@ -0,0 +1,29 @@ +FROM debian:bookworm-slim + +RUN echo deb http://deb.debian.org/debian bookworm-backports main > /etc/apt/sources.list.d/backports.list + +RUN apt-get update -y && apt-get install -y --no-install-recommends curl gnupg bzip2 xz-utils ca-certificates vim procps \ + golang/bookworm-backports golang-go/bookworm-backports golang-doc/bookworm-backports golang-src/bookworm-backports \ + make git python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto 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 ruby3.1-dev lintian npm \ + libc6-dev-i386-cross libc6-dev-armhf-cross libc6-dev-arm64-cross \ + gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +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 + +RUN mkdir /work +WORKDIR /work/src +RUN chown aptly /work + +# install test dependencies +RUN cd /var/lib/aptly; git clone https://github.com/aptly-dev/aptly-fixture-db.git +RUN cd /var/lib/aptly; git clone https://github.com/aptly-dev/aptly-fixture-pool.git +RUN cd /var/lib/aptly; curl -O http://repo.aptly.info/system-tests/etcd.db.xz && xz -d etcd.db.xz +RUN echo "aptly ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/aptly +ADD system/t13_etcd/install-etcd.sh /src/ +RUN /src/install-etcd.sh +RUN rm -rf /src +RUN npm install -g azurite diff --git a/system/api_lib.py b/system/api_lib.py index d6aaf97c..20036b86 100644 --- a/system/api_lib.py +++ b/system/api_lib.py @@ -1,24 +1,41 @@ -from lib import BaseTest -import time -import json -import random -import string -import os import inspect +import json +import os +import random import shutil +import string +import time + +from lib import BaseTest +from testout import TestOut try: import requests except ImportError: requests = None +# States for returned tasks from the API +TASK_SUCCEEDED = 2 +TASK_FAILED = 3 + class APITest(BaseTest): """ BaseTest + testing aptly API """ aptly_server = None + aptly_out = None + debugOutput = True base_url = "127.0.0.1:8765" + configOverride = { + "FileSystemPublishEndpoints": { + "apiandserve": { + "rootDir": f"{os.environ['HOME']}/{BaseTest.aptlyDir}/apiandserve", + "linkMethod": "symlink" + } + }, + "enableSwaggerEndpoint": True + } def fixture_available(self): return super(APITest, self).fixture_available() and requests is not None @@ -27,11 +44,21 @@ class APITest(BaseTest): if APITest.aptly_server is None: super(APITest, self).prepare() - APITest.aptly_server = self._start_process("aptly api serve -no-lock -listen=%s" % (self.base_url),) - time.sleep(1) + APITest.aptly_out = TestOut() - if os.path.exists(os.path.join(os.environ["HOME"], ".aptly", "upload")): - shutil.rmtree(os.path.join(os.environ["HOME"], ".aptly", "upload")) + configPath = os.path.join(os.environ["HOME"], self.aptlyConfigFile) + APITest.aptly_server = self._start_process(f"aptly api serve -no-lock -config={configPath} -listen={self.base_url}", stdout=APITest.aptly_out, stderr=APITest.aptly_out) + time.sleep(1) + else: + APITest.aptly_out.clear() + + if os.path.exists(os.path.join(os.environ["HOME"], self.aptlyDir, "upload")): + shutil.rmtree(os.path.join(os.environ["HOME"], self.aptlyDir, "upload")) + + self.prepare_fixture() + + def debug_output(self): + return APITest.aptly_out.get_contents() def run(self): pass @@ -57,7 +84,7 @@ class APITest(BaseTest): self._ensure_async(kwargs) resp = self.post(uri, *args, **kwargs) if resp.status_code != 202: - return resp + raise Exception("async api error: " + resp.text) _id = resp.json()['ID'] resp = self.get("/api/tasks/" + str(_id) + "/wait") @@ -65,6 +92,20 @@ class APITest(BaseTest): return self.get("/api/tasks/" + str(_id)) + def check_task(self, task): + self.check_equal(task.status_code, 200) + if task.json()['State'] != TASK_SUCCEEDED: + resp2 = self.get("/api/tasks/" + str(task.json()['ID']) + "/output") + raise Exception(f"task failed: {resp2.text}") + + def check_task_fail(self, task, expected_output=None): + self.check_equal(task.status_code, 200) + if task.json()['State'] == TASK_SUCCEEDED: + raise Exception("task expected to fail") + if expected_output: + resp = self.get("/api/tasks/" + str(task.json()['ID']) + "/output") + self.check_equal(resp.json(), expected_output) + def put(self, uri, *args, **kwargs): if "json" in kwargs: kwargs["data"] = json.dumps(kwargs.pop("json")) @@ -128,6 +169,8 @@ class APITest(BaseTest): cls.aptly_server.terminate() cls.aptly_server.wait() cls.aptly_server = None + if APITest.aptly_out is not None: + APITest.aptly_out.close() def random_name(self): return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(15)) diff --git a/system/azure_lib.py b/system/azure_lib.py new file mode 100644 index 00000000..2b871d7b --- /dev/null +++ b/system/azure_lib.py @@ -0,0 +1,115 @@ +from lib import BaseTest +import uuid +import os + +try: + from azure.storage.blob import BlobServiceClient + + azure_storage_account = os.environ.get('AZURE_STORAGE_ACCOUNT') + azure_storage_access_key = os.environ.get('AZURE_STORAGE_ACCESS_KEY') + azure_storage_endpoint = os.environ.get( + 'AZURE_STORAGE_ENDPOINT', + f'https://{azure_storage_account}.blob.core.windows.net', + ) + if azure_storage_account is not None and azure_storage_access_key is not None: + blob_client = BlobServiceClient( + account_url=azure_storage_endpoint, + credential=azure_storage_access_key, + ) + else: + print('Azure tests disabled: Azure creds not found in the environment') + blob_client = None +except ImportError as e: + print("Azure tests disabled: can't import azure.storage.blob", e) + blob_client = None + + +class AzureTest(BaseTest): + """ + BaseTest + support for Azure Blob Storage + """ + + use_azure_pool = False + + def __init__(self) -> None: + super(AzureTest, self).__init__() + self.container_name = None + self.container = None + self.container_contents = None + + def fixture_available(self): + return super(AzureTest, self).fixture_available() and blob_client is not None + + def prepare(self): + self.container_name = 'aptly-sys-test-' + str(uuid.uuid1()) + self.container = blob_client.create_container( + self.container_name, public_access='blob' + ) + + self.azure_endpoint = { + 'accountName': azure_storage_account, + 'accountKey': azure_storage_access_key, + 'container': self.container_name, + 'endpoint': azure_storage_endpoint, + } + + self.configOverride = { + 'AzurePublishEndpoints': { + 'test1': self.azure_endpoint, + }, + } + if self.use_azure_pool: + self.configOverride['packagePoolStorage'] = { + 'type': 'azure', + **self.azure_endpoint, + } + + super(AzureTest, self).prepare() + + def shutdown(self): + if self.container_name is not None: + blob_client.delete_container(self.container_name) + + super(AzureTest, self).shutdown() + + def check_path(self, path): + if self.container_contents is None: + self.container_contents = [ + p.name for p in self.container.list_blobs() if p.name is not None + ] + + if path.startswith('public/'): + path = path.removeprefix('public/') + + if path in self.container_contents: + return True + + if not path.endswith('/'): + path = path + '/' + + for item in self.container_contents: + if item.startswith(path): + return True + + return False + + def check_exists(self, path): + if not self.check_path(path): + raise Exception("path %s doesn't exist" % (path,)) + + def check_exists_azure_only(self, path): + self.check_exists(path) + BaseTest.check_not_exists(self, path) + + def check_not_exists(self, path): + if self.check_path(path): + raise Exception('path %s exists' % (path,)) + + def read_file(self, path, mode=''): + assert not mode + + if path.startswith('public/'): + path = path.removeprefix('public/') + + blob = self.container.download_blob(path) + return blob.readall().decode('utf-8') diff --git a/system/docker-wrapper b/system/docker-wrapper new file mode 100755 index 00000000..1d92f392 --- /dev/null +++ b/system/docker-wrapper @@ -0,0 +1,18 @@ +#!/bin/sh -e + +# make sure files are written with correct user ownership +if [ `stat -c %u /work/src` -ne 0 ]; then + usermod -u `stat -c %u /work/src` aptly >/dev/null + chown -R `stat -c %u /work/src` /var/lib/aptly +fi + +args="$@" +if [ -z "$args" ]; then + cp /work/src/completion.d/aptly /usr/share/bash-completion/completions/ + cmd="bash" +else + cmd="make $@" +fi + +cd /work/src +sudo -u aptly PATH=$PATH:/work/src/build GOPATH=/work/src/.go $cmd diff --git a/pgp/keyrings/aptly2.pub.armor b/system/files/aptly2.pub.armor similarity index 100% rename from pgp/keyrings/aptly2.pub.armor rename to system/files/aptly2.pub.armor diff --git a/pgp/keyrings/aptly2.sec.armor b/system/files/aptly2.sec.armor similarity index 100% rename from pgp/keyrings/aptly2.sec.armor rename to system/files/aptly2.sec.armor diff --git a/pgp/keyrings/aptly2_passphrase.pub.armor b/system/files/aptly2_passphrase.pub.armor similarity index 100% rename from pgp/keyrings/aptly2_passphrase.pub.armor rename to system/files/aptly2_passphrase.pub.armor diff --git a/pgp/keyrings/aptly2_passphrase.sec.armor b/system/files/aptly2_passphrase.sec.armor similarity index 100% rename from pgp/keyrings/aptly2_passphrase.sec.armor rename to system/files/aptly2_passphrase.sec.armor diff --git a/pgp/keyrings/aptly2_trusted.pub b/system/files/aptly2_trusted.pub similarity index 100% rename from pgp/keyrings/aptly2_trusted.pub rename to system/files/aptly2_trusted.pub diff --git a/system/files/flat.key b/system/files/flat.key index e6d6d224..f4f63af8 100644 Binary files a/system/files/flat.key and b/system/files/flat.key differ diff --git a/system/files/jenkins.key b/system/files/jenkins.key index cd0eef50..43f7a7c1 100644 --- a/system/files/jenkins.key +++ b/system/files/jenkins.key @@ -1,52 +1,52 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mQINBF6B77kBEACZoUU41uYVDbagtNQrNQsnbx7UkRdu2rdUZLHryTOKv4InT33Z -mR73lTKT/8UlRYUp1kCLFebMTY73/x7Gk7tSQlURthLEGWpP7hpHZ4Co7slExvg4 -U1ULJfte30EqzwzM4vd6RGYSo6oeReP0Zffd8OYXz6mzcMvcTaKoUfsFbUVJS2ps -rmy2Xzu6arXivSVun8srE121bYf3DfM99G4vRf8VameamMRlSUxOThl+0pLULCwq -iAZR6hNbUbU4IOk6ZkaAUAEYzlwbrn0UjTO3iEkACgkOlcd9juB9+o/Vucbymdto -YioOAB6TcAJ/KM2snChmbkcV2at2CP5d+LVa/waYnd36C+TGCNRPZBNGlUrEeiMA -WGw14vUYMtZG3+04Holwhd/iZ3iqvMLjI5Z6SYueGvqzg8lCfeCTXFHmitWllZr6 -NTvcZeBMVnR/SlTI2IdwaO4ODArtsaC26bLesdfLEL3MnrztUQaJ14Vyv9hOb+bQ -VwxzpWrjXq03zmyeMyta6hvsTDgHCiZQivZjBjSHPbHEM10mKVu+oeVcwuSGFp8Z -zxLiHucwjeQDJ006/dJQH//3BO7P//LH4K11a87Ewj/aG2AIxvV4mMzS3YGSni8a -pCH+GA4GHfybTC6ztFAU8tjKS9u2MSpbo/1gKdmvdLFoYSgGLvjD/+ls8QARAQAB +mQINBGQhzisBEAC7yUhIqVCcyCXJWeZZf/BA6/+KguDQpycck0xUomj5ogT1+lwJ +Mnr6XsPFdTt5DgzjHKg6SM8PTIpLpzOcpqIG9eB8MnvtTp6qFSfIdZnEZccTot1e +cArnM2H6yw/4OW+8QHx9Zgj1miiqolVZ1RusHT3cvPdkF4GFTZnChiF0epd+6iKi +Em8gfzECIltl+McYCwjPXlx38p1mwPI0tgQ7GGD1VzjS/GycuD+shM7lPQ9PmCnC +8zkZIBsbGbSTbAYqnARrbczmg9BKCyErfdQJKi6+r/fg6cWAairXsiOlzqCLCBoZ +ssLKkRAc2ib3cm/RHBm+MK2wLZ5q8xgh9e/iBoBOpJXXARvfu67uQjfLPj/o4FwM +ZWxGZlj2b3cL5q+thjGWOliEh15gciGU17vT15YGcEPVMeDVBYKp/Z+TgkJIlUmD +4bU+K89qYCzp/AP3tsozFwazQRultkjuHVCZrJQtCaVu3/wjtkVd101Oj/Gi4ajn +2WU2KkGWkM0jArUCohJPsZodLHj8DAT2V5SqrEq6jF6ONnAlK1MNmPTKAoDmP6LJ +3of4VHcIbGq1p+I6R9292Lv3Avs/uMbWtR7nae4XWT9l49hY3p8gc5rPOs2wzPgV +v8X6vaQSlgjJDaNVPSZCo8hQkqHsoskri5BHVhxBpjaJ0mNKCeSHWfP+RwARAQAB tDJKZW5raW5zIFByb2plY3QgPGplbmtpbnNjaS1ib2FyZEBnb29nbGVncm91cHMu -Y29tPokCVAQTAQoAPhYhBGKpdWv9eAw3fPJLqPzvMudF8sPVBQJege+5AhsDBQkF -o5qABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEPzvMudF8sPVD9YQAI6jJSP2 -kzwUI4aGYU2SFYel5Uk3ps7mbAcMHcxJMsq/WhjSVWYzTTL4RSgNRI1J6fC8C97k -ONYqE0H1SeIPRQ0h8YiAXCoGhzH6IUMKZv2QlSb+GtZOD0c2oBwVdPC1jIjrUZ7N -BEhsnE20teqCyBy+ReognfTeZRjYkWGDoF0PrCqwybYU1DYoe0NsmKVcwrzNOu1s -6vDIkjyZ6eUPoRLdjNUXoMcgnS8fAxyNksfabJtqo3XoCxy38NqoCH9Bft/nyQgE -Tq7D96PcrnjdqLuqndb09TzhEbYMA1OJ/m8rE5eDxXn0hVef5ZTdSbAGDZ3vak4R -6NtkyYsaDgONWDZGNwfZrQiPDm+ziGdcQ61oRp6dldF6PiG/E++yQbrNLmFoLvqg -UCzEkyuh5ws+QeX5CnrFqxGb1KAVjKWo4bWZuG7Aro6aouCBTzqW60rnYWzlUGzd -eWGMmvIJYLpdiN0hOpdr995jDXLCJzds8Qle+Zpv3SCtnXTD9vFyHQhV9y1UycPJ -gQ3FFT0SSXIlnD7aoqDku/jq/pFCeccUqUjEFWeKa6shiXPXcqo3PCwYOhE1WFAn -MrxKcKzKUyabzI1i9Din5udUtWAEuqH7g2yX6kAr3RBBVjrCe7iALBDzXUTNOj0J -w9eTf917JDRcCeYOhDmUM70Cl0gfQUqzhYzzuQINBF6B77kBEAC8sdb/x+CSQWqX -KLEvIAo1qIusCGWICJqPO/3nG6Y2FUEA9HjZU+/i/Q3mbDk4QN/1qoqUiwg27iOa -Q3GwjWn887U7ZK4QqDPaMZL62Vf5OAC0hA2iXnncvAcRCAo/FUhXkhI+p2w76a3U -LffCOUdevTF/eE8c85GeCxUH/h4tmbDmCsWmYakEs8HpZ+CmOWm3Z/oWmrXYOBbO -UuzGVTOo3oUR0f+CjzsrnPW0oFIFAzBmPLfZSFCcuIeR4gosvtvFurTsQ4tuoVpB -Tgu5Dzyc0iJgFjviSA+lVcBEyye/c0pkXaZss0ZbCEtIgTyWptfSUcxDJISYO5ov -em1DgtM7U8jCO3R4D/nRhjFBx5Wup6F2r1mPz+xGvWCry0ryivGTcIaHZHT8QICJ -aFJ6VfDXJzToxEnlrrXK2Y5qM5HmJa7R/uA9mdYHuhbJJ1m8aB6o8EYR9QtIMJ8h -XGX/Bhdxu+8O4EYDO5bpA/A9zcIsIvT77yJTfaDI9FUL3BWefTS1uswake8xqowF -O+YrTyPUkMlH5tbQFXQ6vzWrkt69nSEBW629ronXm3E6R+1i5uEnN/SHsWoTU2v4 -xcP+K/HT7a6AYq3bEp/vUMASSmaI51U3BA2XyxANtWTzDeseHNIMF6tuZuewwcjN -5F8jFI7iFtvrfUPZFPPQ2/vX2SwOlQARAQABiQI8BBgBCgAmFiEEYql1a/14DDd8 -8kuo/O8y50Xyw9UFAl6B77kCGwwFCQWjmoAACgkQ/O8y50Xyw9XGGxAAhcLj80iM -M26mZA+hPxriP27eLI3yLOsuEKjZftcfOV5WC3iaklkZAmMkmIfhwg/QEkLqL4f4 -MdnVj5/aVyygG2czW3Lvg4otjySTjBv+rf55GVK7KomAjJBU6m+IJdCHJbKKuixP -CEy5GXqdnXkaNbU+DAba0dP8lfQXHgpeeAYUushJrEOEVriCyrxyQan2HomJ2zs7 -IoB1fCLI2FGV5MItyCmVOjJJ6ViJoQz6FRL9kvmT/xwMSwmrQMhMgU6zTZcYzdBL -KYkxahUr7ltETFy3LaPnDezX3zT8mNQ9bqfxaMLn/+Ku/AdriMhfzoC9NXvzhZYY -kWVGYwWzFAVOyn3o1A+MRjhT7FikWXQxKlGuGgcXDhbPWR4gUSWlEga1aFVRzYHO -Ty60f6SWcuTgL0tBNcdXqEkhSpoV/9wxhTox6lfaH3vajganr9gPyew1oiOrvIzw -PNbzJTe+Yj1WEUheSdWuI5jPQl5Mr5YYxf9j0T2xqb2k01/FW+aR5Xte5Bj9OGz2 -Ou4QDK481XaUKovplz3jj2PtK8d/12OJT11ukFb8py6u1ezxRjOm3Nk6kzySG8vP -hjqccnUDI8Eg8yo0s02+SVt9lNkVe7ggF99TRp/rMBCxb+e27OPjqdRKEArB8JyI -Z77i4FhdLio04srxWsN+FkRwkBJdkJ8VKEE= -=0xpA +Y29tPokCVwQTAQgAQRYhBGNmfudLuh8KCKaYclujHVfvWXXKBQJkIc4rAhsDBQkF +o5qABQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEFujHVfvWXXK4+kP/0cR +nNYrjb4gWG/rcwJ8zo0YKZBO30RPul1INnyufDediDb0UCOJwT+CnEZULx+HeUOi +xHVBMD70LRP3ym+40Naw3s4nJWvBpOYIqQhjoRqrWkdIrMgNSAwRrufgXqSBvvfZ ++xQYrNRuu8/00U6Bz2eeCL2SNZpShL0iPjP9Bcu7763jaGvnS/WUVaAqqyNwxGRl +afffRvCV/Wjy47W+ifCPgku4SKZgG+QPMuthI842+lLSl2BXhiEVJ4auK5rjFHsv +RrUEQrjEGZ9vEoitZAQL/CDWmlkhrqYSpgTVsMCoByRzZqQG9fOAJNFniFqrANQQ +m9NkZN0ZljOnZfJh+ZzbjjVUS522piJtVqdOU0noT6awMtSO/CO4EmuElj8LkVI6 +jbP0FqxYecNQtlAzBguRD5UWjAi3jgkdbap0ooqZm2YQPNaLD3OLWdvtj/jx+EI4 +DTrSoSSoHea5xiAFQNB3ab2fk5kN5ufVWIV5F9AQHU+kWE9jgS+zl8apzbwMinm8 +ZW0KeIcW63MH5hvbmsfBjdyroTTy2mwy096mB2vvqwWv6nt9mQy1YCmBeyp7oshI +qNeXIunP1NekAfGY+dRlldA3SoxNuJhVGd5eCOFWYmipb9XD+JrSgncHjCgewHq1 +ycptdm1q8OZ26ZOaAIVOYENk8WUOz5DzOuOS81EJuQINBGQhzisBEADtvyAOnz23 +gKKKVzSY9bhEvQxJWQUY/jXek7LjhflLw4xugGARMrTMc6zzabOJefyrVkucWqso +spCWoj+M8HGfhHXpNDHbn21fyHB6jpOh8Ors2ZHH2skswcAcWcLlWQWrUtqQFuje +1rXShp8IhYz308MIZ65VYf/Z9Bk7VNNTgRLmOMTn+KlN8qiQZ0SZbPj/wFK1iwAh +/xPu586a7xVN4xdy6RJNfrSCG84kMNyaHTDFOEKchWPoGe5D6EqF8dufvrcKoSxc +T2sC6WzDqG/+Jfk//xrHblxeCXiOAX/Dm+McvceV0dBSVJJx67FoHUyWBRj5coHU +4YfrUTHREEKdYcpUAHQGPJBLyx0QNs2URhYSCwNU5yYL+z3UIpsS93HosUPEzrTD +XE1D2eV1gGf0YzCWxWTAuOjoUD8D22p//GAaLXYpuwSgVzgKwvPefkWJ94Euvz6T +sKrljMPsxOdPLBs8AJgrqmYIwbRXoxNEzv/PT/9sST5nl5tlWc9PonzwzHqStU4Y +f8jQhIv1yq2wAE2OB0Q7B6i62QWqSWAWEAc6LPRdSalgS8ooj/MIQFGwsd4VuNSN +JD9p7bHHlHceeXMR2F0JeG8G91RqlTkxu7cUMkqheXXAyTa/OuG5xauHyLzt4xVp +fnHd5fNjxcc02ADF46X6/nze6hClUBqMAQARAQABiQI8BBgBCAAmFiEEY2Z+50u6 +HwoIpphyW6MdV+9ZdcoFAmQhzisCGwwFCQWjmoAACgkQW6MdV+9ZdcoRGA/+JmjW +09ZmAlBM846GgI0B00YtXMu3PuhhOq8sJEXvcvlCfSAlVpHfnwUJE7q5QaUrD3wT +VKT4pe/zBRN+zD84gXxGANJY813EhpngBEJmptIjNkKvWclr/nG4MI8yezZmeEgP +142LviJmNYb0+3s1CU7Q03g3b/wsHNFpuA9zVJu24xVAM/Af65N1STvnSQAjcXa9 +rgIwdiZ7XbCD6rpF1ms8i6RYsflB+dGLgEOiAlX+lZ6843WpMWlDUBd2v+OHtXvm +zLYbg8SYtHV8xMJWPjz6e9yoKuyjvWAwAiDcjO0SpCqlkHsUzWRS44z3hQssgywP +iFKGqP5eHDaSCqUHF5VkGdtg/a9M7vthhEoB/2IKSf82CQE9IdmNtEJHAPgWamgm +VPpyMliDTd2gyqD+FmduRdY/yHMP0QV6G/VRTV4gfQ80qU/U2JXWAQdw6ok1+k5V +t0ur8buQo+49diyr8WPHA4CwpSwriwIClDZdq38JiCdnfICfFxAQYdBMbL6S4wqA +Sv+OqcDBvu7m5yV/hrfcVztRkWUwr21kUmvx04xpvvpG/cUAnQOog3Q7Ce5xkaX7 +99Ewd0xUXma/H++IGX77jxU7jW5n2FPeVEn+zcNF8of/XAi1uaP1WL5T/iEl6EsI +MetBbjkOnNXyWrP3SAPwqQMMg/vNa+mJIjoNByw= +=sdsH -----END PGP PUBLIC KEY BLOCK----- diff --git a/system/fixture.sh b/system/fixture.sh index e5474bb1..81b26aee 100644 --- a/system/fixture.sh +++ b/system/fixture.sh @@ -22,11 +22,11 @@ aptly mirror update wheezy-non-free-src aptly mirror update wheezy-updates-src aptly mirror update wheezy-backports-src -aptly mirror create gnuplot-maverick http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick +aptly mirror create gnuplot-maverick http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick aptly mirror update gnuplot-maverick -aptly mirror create -with-sources gnuplot-maverick-src http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick +aptly mirror create -with-sources gnuplot-maverick-src http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick aptly mirror update gnuplot-maverick-src aptly mirror create sensu http://repos.sensuapp.org/apt sensu -aptly mirror update sensu \ No newline at end of file +aptly mirror update sensu diff --git a/system/leveldb2etcd.py b/system/leveldb2etcd.py new file mode 100644 index 00000000..65788b12 --- /dev/null +++ b/system/leveldb2etcd.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +import plyvel +import etcd3 +import argparse +from termcolor import cprint + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--datadir", required=True, help="leveldb data dir") + parser.add_argument("--etcdaddr", default="127.0.0.1", help="etcd server address") + parser.add_argument("--etcdport", default="2379", help="etcd server address") + + args = parser.parse_args() + + ldb = plyvel.DB(args.datadir) + etcd = etcd3.client(args.etcdaddr, args.etcdport) + + for key, value in ldb: + try: + keystr = key + valuestr = value + etcd.put(keystr, valuestr) + # cprint("key: "+keystr+", value: "+valuestr+"put success!\n", 'green') + except Exception as e: + cprint("key: " + str(keystr) + ", value: " + str(valuestr) + "put err: " + str(e) + "\n", 'red') + exit(1) diff --git a/system/lib.py b/system/lib.py index 013cfc3a..1287f81d 100644 --- a/system/lib.py +++ b/system/lib.py @@ -3,24 +3,27 @@ Test library. """ import difflib +import http.server import inspect import json -import subprocess import os import posixpath +import pprint import re import shlex import shutil +import socketserver import string +import subprocess import threading import urllib.error import urllib.parse import urllib.request -import pprint -import socketserver -import http.server import zlib +from pathlib import Path +from uuid import uuid4 + def ungzip_if_required(output): if isinstance(output, bytes) and output.startswith(b"\x1f\x8b"): @@ -100,7 +103,7 @@ class DotFinder(object): def find_dot(self, executables): for executable in executables: try: - subprocess.check_output([executable, "-V"], text=True) + subprocess.check_output([executable, "-V"], text=True, stderr=subprocess.DEVNULL) return executable except Exception: pass @@ -125,25 +128,15 @@ class BaseTest(object): requiresGPG2 = False requiresDot = False sortOutput = False + debugOutput = False + EtcdServer = None + aptlyDir = ".aptly" + aptlyConfigFile = ".aptly.conf" expectedCode = 0 - configFile = { - "rootDir": "%s/.aptly" % os.environ["HOME"], - "downloadConcurrency": 4, - "downloadSpeedLimit": 0, - "downloadRetries": 5, - "databaseOpenAttempts": 10, - "architectures": [], - "dependencyFollowSuggests": False, - "dependencyFollowRecommends": False, - "dependencyFollowAllVariants": False, - "dependencyFollowSource": False, - "gpgDisableVerify": False, - "gpgDisableSign": False, - "ppaDistributorID": "ubuntu", - "ppaCodename": "", - "enableMetricsEndpoint": True, - } + databaseType = "" + databaseUrl = "" + configOverride = {} environmentOverride = {} @@ -169,26 +162,55 @@ class BaseTest(object): try: self.run() self.check() + except Exception as exc: + if self.debugOutput: + print(f"API log:\n{self.debug_output()}") + raise exc finally: self.teardown() def prepare_remove_all(self): - if os.path.exists(os.path.join(os.environ["HOME"], ".aptly")): - shutil.rmtree(os.path.join(os.environ["HOME"], ".aptly")) - if os.path.exists(os.path.join(os.environ["HOME"], ".aptly.conf")): - os.remove(os.path.join(os.environ["HOME"], ".aptly.conf")) + if os.path.exists(os.path.join(os.environ["HOME"], self.aptlyDir)): + shutil.rmtree(os.path.join(os.environ["HOME"], self.aptlyDir)) + if os.path.exists(os.path.join(os.environ["HOME"], self.aptlyConfigFile)): + os.remove(os.path.join(os.environ["HOME"], self.aptlyConfigFile)) if os.path.exists(os.path.join(os.environ["HOME"], ".gnupg", "aptlytest.gpg")): os.remove(os.path.join( os.environ["HOME"], ".gnupg", "aptlytest.gpg")) def prepare_default_config(self): - cfg = self.configFile.copy() + databaseBackend = { + "type": self.databaseType, + "url": self.databaseUrl, + } + + cfg = { + "rootDir": f"{os.environ['HOME']}/{self.aptlyDir}", + "downloadConcurrency": 4, + "downloadSpeedLimit": 0, + "downloadRetries": 5, + "databaseOpenAttempts": 10, + "architectures": [], + "dependencyFollowSuggests": False, + "dependencyFollowRecommends": False, + "dependencyFollowAllVariants": False, + "dependencyFollowSource": False, + "gpgDisableVerify": False, + "gpgDisableSign": False, + "ppaDistributorID": "ubuntu", + "ppaCodename": "", + "enableMetricsEndpoint": True, + "logLevel": "debug", + "logFormat": "default", + "serveInAPIMode": True, + "databaseBackend": databaseBackend, + } if self.requiresGPG1: cfg["gpgProvider"] = "gpg1" elif self.requiresGPG2: cfg["gpgProvider"] = "gpg2" cfg.update(**self.configOverride) - f = open(os.path.join(os.environ["HOME"], ".aptly.conf"), "w") + f = open(os.path.join(os.environ["HOME"], self.aptlyConfigFile), "w") f.write(json.dumps(cfg)) f.close() @@ -210,24 +232,42 @@ class BaseTest(object): def prepare_fixture(self): if self.fixturePool: - os.makedirs(os.path.join(os.environ["HOME"], ".aptly"), 0o755) + os.makedirs(os.path.join(os.environ["HOME"], self.aptlyDir), 0o755) os.symlink(self.fixturePoolDir, os.path.join( - os.environ["HOME"], ".aptly", "pool")) + os.environ["HOME"], self.aptlyDir, "pool")) if self.fixturePoolCopy: - os.makedirs(os.path.join(os.environ["HOME"], ".aptly"), 0o755) + os.makedirs(os.path.join(os.environ["HOME"], self.aptlyDir), 0o755) shutil.copytree(self.fixturePoolDir, os.path.join( - os.environ["HOME"], ".aptly", "pool"), ignore=shutil.ignore_patterns(".git")) + os.environ["HOME"], self.aptlyDir, "pool"), ignore=shutil.ignore_patterns(".git")) - if self.fixtureDB: - shutil.copytree(self.fixtureDBDir, os.path.join( - os.environ["HOME"], ".aptly", "db")) + if self.databaseType == "etcd": + if not os.path.exists("/tmp/aptly-etcd"): + self.run_cmd([os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "t13_etcd/install-etcd.sh")]) + + if self.fixtureDB and self.databaseType != "etcd": + shutil.copytree(self.fixtureDBDir, os.path.join(os.environ["HOME"], self.aptlyDir, "db")) + + if self.databaseType == "etcd": + if self.EtcdServer: + self.shutdown_etcd() + + # remove existing database + if os.path.exists("/tmp/aptly-etcd-data"): + shutil.rmtree("/tmp/aptly-etcd-data") + + if self.fixtureDB: + print("import etcd") + self.run_cmd(["/tmp/aptly-etcd/etcdctl", "--data-dir=/tmp/aptly-etcd-data", "snapshot", "restore", os.path.join(os.environ["HOME"], "etcd.db")]) + + print("starting etcd") + self.EtcdServer = self._start_process([os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "t13_etcd/start-etcd.sh")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if self.fixtureWebServer: self.webServerUrl = self.start_webserver(os.path.join(os.path.dirname(inspect.getsourcefile(self.__class__)), self.fixtureWebServer)) - if self.requiresGPG2: + if self.requiresGPG2 or self.gpgFinder.gpg2: self.run_cmd([ self.gpgFinder.gpg2, "--import", os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly.sec"], expected_code=None) @@ -235,10 +275,14 @@ class BaseTest(object): if self.fixtureGpg: self.run_cmd([self.gpgFinder.gpg, "--no-default-keyring", "--trust-model", "always", "--batch", "--keyring", "aptlytest.gpg", "--import"] + [os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files", key) for key in self.fixtureGpgKeys]) + self.run_cmd(["chmod", "400", os.path.join(os.environ["HOME"], ".gnupg/aptlytest.gpg")]) if hasattr(self, "fixtureCmds"): for cmd in self.fixtureCmds: - self.run_cmd(cmd) + output = self.run_cmd(cmd) + print("fixture Output:\n") + for line in output.split("\n"): + print(f" {line}") def sort_lines(self, output): return "\n".join(sorted(self.ensure_utf8(output).split("\n"))) @@ -256,31 +300,55 @@ class BaseTest(object): 'changes': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "changes"), 'udebs': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "udebs"), 'testfiles': os.path.join(os.path.dirname(inspect.getsourcefile(self.__class__)), self.__class__.__name__), - 'aptlyroot': os.path.join(os.environ["HOME"], ".aptly"), + 'aptlyroot': os.path.join(os.environ["HOME"], self.aptlyDir), } if self.fixtureWebServer: params['url'] = self.webServerUrl command = string.Template(command).substitute(params) + print(f"running command: {command}\n") command = shlex.split(command) + if command[0] == "aptly": + aptly_testing_bin = Path(__file__).parent / ".." / "aptly.test" + command = [str(aptly_testing_bin), f"-test.coverprofile={Path(self.coverage_dir) / self.__class__.__name__}-{uuid4()}.out", *command[1:]] + environ = os.environ.copy() environ["LC_ALL"] = "C" environ.update(self.environmentOverride) return subprocess.Popen(command, stderr=stderr, stdout=stdout, env=environ) def run_cmd(self, command, expected_code=0): - try: - proc = self._start_process(command, stdout=subprocess.PIPE) - output, _ = proc.communicate() - if expected_code is not None: - if proc.returncode != expected_code: - raise Exception("exit code %d != %d (output: %s)" % ( - proc.returncode, expected_code, output)) - return output - except Exception as e: - raise Exception("Running command %s failed: %s" % - (command, str(e))) + proc = self._start_process(command, stdout=subprocess.PIPE) + raw_output, _ = proc.communicate() + + raw_output = raw_output.decode("utf-8", errors='replace') + + returncodes = [proc.returncode] + is_aptly_command = False + if isinstance(command, str): + is_aptly_command = command.startswith("aptly") + + if isinstance(command, list): + is_aptly_command = command[0] == "aptly" + + if is_aptly_command: + # remove the last two rows as go tests always print PASS/FAIL and coverage in those + # two lines. This would otherwise fail the tests as they would not match gold + matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output) + if not matches: + raise Exception("no matches found in command output '%s'" % raw_output) + + output, _, returncode = matches[0] + returncodes.append(int(returncode)) + else: + output = raw_output + + if expected_code is not None: + if expected_code not in returncodes: + raise Exception("command expected to return %d, but returned %d: \n%s" % ( + expected_code, proc.returncode, raw_output)) + return output def gold_processor(self, gold): return gold @@ -307,10 +375,12 @@ class BaseTest(object): return s def check_output(self): + gold_file = self.get_gold_filename() + print(f"Verifying gold file: {gold_file}") try: self.verify_match(self.get_gold(), self.output, match_prepare=self.outputMatchPrepare) - except: # noqa: E722 + except Exception: # noqa: E722 if self.captureResults: if self.outputMatchPrepare is not None: self.output = self.outputMatchPrepare(self.output) @@ -332,12 +402,21 @@ class BaseTest(object): else: raise + def write_file(self, path, content): + full_path = os.path.join(os.environ["HOME"], ".aptly", path) + + if not os.path.exists(os.path.dirname(full_path)): + os.makedirs(os.path.dirname(full_path), 0o755) + + with open(full_path, "w") as f: + f.write(content) + def read_file(self, path, mode=''): - with open(os.path.join(os.environ["HOME"], ".aptly", path), "r" + mode) as f: + with open(os.path.join(os.environ["HOME"], self.aptlyDir, path), "r" + mode) as f: return f.read() def delete_file(self, path): - os.unlink(os.path.join(os.environ["HOME"], ".aptly", path)) + os.unlink(os.path.join(os.environ["HOME"], self.aptlyDir, path)) def check_file_contents(self, path, gold_name, match_prepare=None, mode='', ensure_utf8=True): contents = self.read_file(path, mode=mode) @@ -367,15 +446,15 @@ class BaseTest(object): raise def check_exists(self, path): - if not os.path.exists(os.path.join(os.environ["HOME"], ".aptly", path)): + if not os.path.exists(os.path.join(os.environ["HOME"], self.aptlyDir, path)): raise Exception("path %s doesn't exist" % (path, )) def check_not_exists(self, path): - if os.path.exists(os.path.join(os.environ["HOME"], ".aptly", path)): + if os.path.exists(os.path.join(os.environ["HOME"], self.aptlyDir, path)): raise Exception("path %s exists" % (path, )) def check_file_not_empty(self, path): - if os.stat(os.path.join(os.environ["HOME"], ".aptly", path))[6] == 0: + if os.stat(os.path.join(os.environ["HOME"], self.aptlyDir, path))[6] == 0: raise Exception("file %s is empty" % (path, )) def check_equal(self, a, b): @@ -392,11 +471,11 @@ class BaseTest(object): def check_in(self, item, l): if item not in l: - raise Exception("item %r not in %r", item, l) + raise Exception("expected item: %r\nnot found in: %r" % (item, l)) def check_not_in(self, item, l): if item in l: - raise Exception("item %r in %r", item, l) + raise Exception("unexpected item: %r\n found in: %r" % (item, l)) def check_subset(self, a, b): diff = '' @@ -407,7 +486,7 @@ class BaseTest(object): diff += "wrong value '%s' for key '%s', expected '%s'\n" % ( v, k, b[k]) if diff: - raise Exception("content doesn't match:\n" + diff) + raise Exception("content subset doesn't match:\n" + diff) def ensure_utf8(self, a): if isinstance(a, bytes): @@ -423,6 +502,10 @@ class BaseTest(object): a = match_prepare(a) b = match_prepare(b) + # strip trailing whitespace and newlines + a = a.strip() + b = b.strip() + if a != b: diff = "".join(difflib.unified_diff( [l + "\n" for l in a.split("\n")], [l + "\n" for l in b.split("\n")])) @@ -437,7 +520,8 @@ class BaseTest(object): self.prepare_fixture() def teardown(self): - pass + if self.EtcdServer: + self.shutdown_etcd() def start_webserver(self, directory): FileHTTPServerRequestHandler.rootPath = directory @@ -453,10 +537,18 @@ class BaseTest(object): def shutdown(self): if hasattr(self, 'webserver'): self.shutdown_webserver() + if self.EtcdServer: + self.shutdown_etcd() def shutdown_webserver(self): self.webserver.shutdown() + def shutdown_etcd(self): + print("stopping etcd") + self.EtcdServer.terminate() + self.EtcdServer.wait() + self.EtcdServer = None + @classmethod def shutdown_class(cls): pass diff --git a/system/requirements.txt b/system/requirements.txt index 3d4be24f..b0627340 100644 --- a/system/requirements.txt +++ b/system/requirements.txt @@ -1,6 +1,9 @@ +azure-storage-blob boto -requests +requests==2.28.2 requests-unixsocket python-swiftclient flake8 termcolor +etcd3 +leveldb diff --git a/system/run.py b/system/run.py index e27377d0..599afe5f 100755 --- a/system/run.py +++ b/system/run.py @@ -7,15 +7,19 @@ import inspect import fnmatch import re import sys +from tempfile import mkdtemp import traceback import random import subprocess +import time from lib import BaseTest from s3_lib import S3Test from swift_lib import SwiftTest +from azure_lib import AzureTest from api_lib import APITest from fs_endpoint_lib import FileSystemEndpointTest +from testout import TestOut try: from termcolor import colored @@ -32,35 +36,56 @@ def natural_key(string_): return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)] -def walk_modules(package): - yield importlib.import_module(package) - for name in sorted(glob.glob(package + "/*.py"), key=natural_key): - name = os.path.splitext(os.path.basename(name))[0] - if name == "__init__": - continue - - yield importlib.import_module(package + "." + name) - - -def run(include_long_tests=False, capture_results=False, tests=None, filters=None): +def run(include_long_tests=False, capture_results=False, tests=None, filters=None, coverage_dir=None): """ Run system test. """ + print(colored("\n Aptly System Tests\n====================\n", color="green", attrs=["bold"])) + if not tests: tests = sorted(glob.glob("t*_*"), key=natural_key) fails = [] numTests = numFailed = numSkipped = 0 lastBase = None + if not coverage_dir: + coverage_dir = mkdtemp(suffix="aptly-coverage") for test in tests: - for testModule in walk_modules(test): - for name in sorted(dir(testModule), key=natural_key): - o = getattr(testModule, name) + orig_stdout = sys.stdout + orig_stderr = sys.stderr - if not (inspect.isclass(o) and issubclass(o, BaseTest) and o is not BaseTest and - o is not SwiftTest and o is not S3Test and o is not APITest and o is not FileSystemEndpointTest): + # importlib.import_module(test) + for fname in sorted(glob.glob(test + "/*.py"), key=natural_key): + fname = os.path.splitext(os.path.basename(fname))[0] + if fname == "__init__": + continue + + testout = TestOut() + sys.stdout = testout + sys.stderr = testout + + try: + testModule = importlib.import_module(test + "." + fname) + except Exception as exc: + orig_stdout.write(f"error importing: {test + '.' + fname}: {exc}\n") + continue + + testignore = [] + if hasattr(testModule, "TEST_IGNORE"): + testignore = testModule.TEST_IGNORE + for name in sorted(dir(testModule), key=natural_key): + if name in testignore: continue + o = getattr(testModule, name) + if not (inspect.isclass(o) and issubclass(o, BaseTest) and o is not BaseTest and + o is not SwiftTest and o is not S3Test and o is not AzureTest and + o is not APITest and o is not FileSystemEndpointTest): + continue + + testout.clear() + start_time = time.time() + newBase = o.__bases__[0] if lastBase is not None and lastBase is not newBase: lastBase.shutdown_class() @@ -78,77 +103,108 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non if not matches: continue - sys.stdout.write("%s:%s... " % (test, o.__name__)) - sys.stdout.flush() + orig_stdout.write("· %-13s ➔ %-48s ... " % (test, colored(o.__name__, color="yellow", attrs=["bold"]))) + orig_stdout.flush() t = o() + if t.longTest and not include_long_tests or not t.fixture_available() or t.skipTest: numSkipped += 1 msg = 'SKIP' if t.skipTest and t.skipTest is not True: # If we have a reason to skip, print it msg += ': ' + t.skipTest - sys.stdout.write(colored(msg + "\n", color="yellow")) + orig_stdout.write(colored(msg + "\n", color="yellow")) continue numTests += 1 + failed = False + t.captureResults = capture_results + t.coverage_dir = coverage_dir + typ = None + val = None + tb = None try: - t.captureResults = capture_results t.test() except Exception: - numFailed += 1 typ, val, tb = sys.exc_info() - fails.append((test, t, typ, val, tb, testModule)) - traceback.print_exception(typ, val, tb) - sys.stdout.write(colored("FAIL\n", color="red")) + failed = True + + end_time = time.time() + execution_time = int(end_time - start_time) + 1 + minutes = execution_time // 60 + seconds = execution_time % 60 + if minutes > 0: + minutes = f"{minutes}m" + if seconds < 10: + seconds = f"0{seconds}" else: - sys.stdout.write(colored("OK\n", color="green")) + minutes = " " + if seconds < 10: + seconds = f" {seconds}" + duration = f"{minutes}{seconds}s" + + if failed: + numFailed += 1 + fails.append((test, t, typ, val, tb, testModule)) + orig_stdout.write(colored("\b\b\b\bFAIL", color="red", attrs=["bold"]) + f" {duration}\n") + + orig_stdout.write(testout.get_contents()) + traceback.print_exception(typ, val, tb, file=orig_stdout) + else: + orig_stdout.write(colored("\b\b\b\bOK", color="green", attrs=["bold"]) + f" {duration}\n") t.shutdown() + sys.stdout = orig_stdout + sys.stderr = orig_stderr + if lastBase is not None: lastBase.shutdown_class() - print("TESTS: %d SUCCESS: %d FAIL: %d SKIP: %d" % ( - numTests, numTests - numFailed, numFailed, numSkipped)) + print("\nCOVERAGE_RESULTS: %s" % coverage_dir) + + print(f"TESTS: {numTests} ", + colored(f"SUCCESS: {numTests - numFailed} ", color="green", attrs=["bold"]) if numFailed == 0 else + f"SUCCESS: {numTests - numFailed} ", + colored(f"FAIL: {numFailed} ", color="red", attrs=["bold"]) if numFailed > 0 else "FAIL: 0 ", + colored(f"SKIP: {numSkipped}", color="yellow", attrs=["bold"]) if numSkipped > 0 else "SKIP: 0") + print() if len(fails) > 0: - print("\nFAILURES (%d):" % (len(fails), )) + print(colored("FAILURES (%d):" % (len(fails), ), color="red", attrs=["bold"])) for (test, t, typ, val, tb, testModule) in fails: doc = t.__doc__ or '' - print("%s:%s %s" % (test, t.__class__.__name__, - testModule.__name__ + ": " + doc.strip())) - traceback.print_exception(typ, val, tb) - print("=" * 60) - + print(" - %s: %s %s" % (test, colored(t.__class__.__name__, color="yellow", attrs=["bold"]), + testModule.__name__ + ": " + doc.strip())) + print() sys.exit(1) if __name__ == "__main__": - if 'APTLY_VERSION' not in os.environ: - try: - os.environ['APTLY_VERSION'] = os.popen( - "make version").read().strip() - except BaseException as e: - print("Failed to capture current version: ", e) + try: + os.environ['APTLY_VERSION'] = os.popen("make -s version").read().strip() + except BaseException as e: + print("Failed to capture current version: ", e) if sys.version_info < PYTHON_MINIMUM_VERSION: raise RuntimeError(f'Tests require Python {PYTHON_MINIMUM_VERSION} or higher.') - output = subprocess.check_output(['gpg1', '--version'], text=True) - if not output.startswith('gpg (GnuPG) 1'): - raise RuntimeError('Tests require gpg v1') + output = subprocess.check_output(['gpg', '--version'], text=True) + if not output.startswith('gpg (GnuPG) 2'): + raise RuntimeError('Tests require gpg v2') - output = subprocess.check_output(['gpgv1', '--version'], text=True) - if not output.startswith('gpgv (GnuPG) 1'): - raise RuntimeError('Tests require gpgv v1') + output = subprocess.check_output(['gpgv', '--version'], text=True) + if not output.startswith('gpgv (GnuPG) 2'): + raise RuntimeError('Tests require gpgv v2') os.chdir(os.path.realpath(os.path.dirname(sys.argv[0]))) random.seed() include_long_tests = False capture_results = False + coverage_dir = None tests = None args = sys.argv[1:] @@ -157,6 +213,9 @@ if __name__ == "__main__": include_long_tests = True elif args[0] == "--capture": capture_results = True + elif args[0] == "--coverage-dir": + coverage_dir = args[1] + args = args[1:] args = args[1:] @@ -169,4 +228,4 @@ if __name__ == "__main__": else: filters.append(arg) - run(include_long_tests, capture_results, tests, filters) + run(include_long_tests, capture_results, tests, filters, coverage_dir) diff --git a/system/s3_lib.py b/system/s3_lib.py index 9940fe13..4a52b9ad 100644 --- a/system/s3_lib.py +++ b/system/s3_lib.py @@ -5,10 +5,11 @@ import os try: import boto - if 'AWS_SECRET_ACCESS_KEY' in os.environ and 'AWS_ACCESS_KEY_ID' in os.environ: + if 'AWS_SECRET_ACCESS_KEY' in os.environ and 'AWS_ACCESS_KEY_ID' in os.environ and \ + os.environ['AWS_SECRET_ACCESS_KEY'] != "" and os.environ['AWS_ACCESS_KEY_ID'] != "": s3_conn = boto.connect_s3() else: - print("S3 tests disabled: AWS creds not found in the environment") + print("S3 tests disabled: AWS creds not found in the environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)") s3_conn = None except ImportError as e: print("S3 tests disabled: can't import boto", e) @@ -32,6 +33,8 @@ class S3Test(BaseTest): "test1": { "region": "us-east-1", "bucket": self.bucket_name, + "awsAccessKeyID": os.environ["AWS_ACCESS_KEY_ID"], + "awsSecretAccessKey": os.environ["AWS_SECRET_ACCESS_KEY"] } }} @@ -76,7 +79,10 @@ class S3Test(BaseTest): if self.check_path(path): raise Exception("path %s exists" % (path, )) - def read_file(self, path): + def read_file(self, path, mode=''): + # We don't support reading as binary here. + assert not mode + if path.startswith("public/"): path = path[7:] diff --git a/system/t01_version/__init__.py b/system/t01_version/__init__.py index 388e7df6..e0a28e39 100644 --- a/system/t01_version/__init__.py +++ b/system/t01_version/__init__.py @@ -1,14 +1,3 @@ """ Test aptly version """ - -from lib import BaseTest - - -class VersionTest(BaseTest): - """ - version should match - """ - gold_processor = BaseTest.expand_environ - - runCmd = "aptly version" diff --git a/system/t01_version/version.py b/system/t01_version/version.py new file mode 100644 index 00000000..388e7df6 --- /dev/null +++ b/system/t01_version/version.py @@ -0,0 +1,14 @@ +""" +Test aptly version +""" + +from lib import BaseTest + + +class VersionTest(BaseTest): + """ + version should match + """ + gold_processor = BaseTest.expand_environ + + runCmd = "aptly version" diff --git a/system/t02_config/BadConfigTest_gold b/system/t02_config/BadConfigTest_gold index 10f5b8ce..f6864d06 100644 --- a/system/t02_config/BadConfigTest_gold +++ b/system/t02_config/BadConfigTest_gold @@ -1 +1 @@ -ERROR: error loading config file ${HOME}/.aptly.conf: invalid character 's' looking for beginning of object key string +ERROR: error loading config file ${HOME}/.aptly.conf: invalid yaml (yaml: line 1: did not find expected ',' or '}') or json (invalid character 's' looking for beginning of object key string) diff --git a/system/t02_config/ConfigShowTest_gold b/system/t02_config/ConfigShowTest_gold index 57b42b6a..5a4a2273 100644 --- a/system/t02_config/ConfigShowTest_gold +++ b/system/t02_config/ConfigShowTest_gold @@ -1,29 +1,39 @@ { "rootDir": "${HOME}/.aptly", - "downloadConcurrency": 4, - "downloadSpeedLimit": 0, - "downloadRetries": 5, - "downloader": "default", + "logLevel": "debug", + "logFormat": "default", "databaseOpenAttempts": 10, "architectures": [], + "skipLegacyPool": false, "dependencyFollowSuggests": false, "dependencyFollowRecommends": false, "dependencyFollowAllVariants": false, "dependencyFollowSource": false, "dependencyVerboseResolve": false, - "gpgDisableSign": false, - "gpgDisableVerify": false, - "gpgProvider": "gpg", - "downloadSourcePackages": false, - "skipLegacyPool": false, "ppaDistributorID": "ubuntu", "ppaCodename": "", + "serveInAPIMode": true, + "enableMetricsEndpoint": true, + "enableSwaggerEndpoint": false, + "AsyncAPI": false, + "databaseBackend": { + "type": "", + "dbPath": "", + "url": "" + }, + "downloader": "default", + "downloadConcurrency": 4, + "downloadSpeedLimit": 0, + "downloadRetries": 5, + "downloadSourcePackages": false, + "gpgProvider": "gpg", + "gpgDisableSign": false, + "gpgDisableVerify": false, "skipContentsPublishing": false, "skipBz2Publishing": false, "FileSystemPublishEndpoints": {}, "S3PublishEndpoints": {}, "SwiftPublishEndpoints": {}, "AzurePublishEndpoints": {}, - "AsyncAPI": false, - "enableMetricsEndpoint": true + "packagePoolStorage": {} } diff --git a/system/t02_config/ConfigShowYAMLTest_gold b/system/t02_config/ConfigShowYAMLTest_gold new file mode 100644 index 00000000..615982b0 --- /dev/null +++ b/system/t02_config/ConfigShowYAMLTest_gold @@ -0,0 +1,37 @@ +root_dir: ${HOME}/.aptly +log_level: debug +log_format: default +database_open_attempts: 10 +architectures: [] +skip_legacy_pool: false +dep_follow_suggests: false +dep_follow_recommends: false +dep_follow_all_variants: false +dep_follow_source: false +dep_verboseresolve: false +ppa_distributor_id: ubuntu +ppa_codename: "" +serve_in_api_mode: true +enable_metrics_endpoint: true +enable_swagger_endpoint: false +async_api: false +database_backend: + type: "" + db_path: "" + url: "" +downloader: default +download_concurrency: 4 +download_limit: 0 +download_retries: 5 +download_sourcepackages: false +gpg_provider: gpg +gpg_disable_sign: false +gpg_disable_verify: false +skip_contents_publishing: false +skip_bz2_publishing: false +filesystem_publish_endpoints: {} +s3_publish_endpoints: {} +swift_publish_endpoints: {} +azure_publish_endpoints: {} +packagepool_storage: {} + diff --git a/system/t02_config/CreateConfigTest_gold b/system/t02_config/CreateConfigTest_gold index 7d1a23e1..38a8a56a 100644 --- a/system/t02_config/CreateConfigTest_gold +++ b/system/t02_config/CreateConfigTest_gold @@ -1,29 +1,343 @@ -{ - "rootDir": "${HOME}/.aptly", - "downloadConcurrency": 4, - "downloadSpeedLimit": 0, - "downloadRetries": 0, - "downloader": "default", - "databaseOpenAttempts": -1, - "architectures": [], - "dependencyFollowSuggests": false, - "dependencyFollowRecommends": false, - "dependencyFollowAllVariants": false, - "dependencyFollowSource": false, - "dependencyVerboseResolve": false, - "gpgDisableSign": false, - "gpgDisableVerify": false, - "gpgProvider": "gpg", - "downloadSourcePackages": false, - "skipLegacyPool": true, - "ppaDistributorID": "ubuntu", - "ppaCodename": "", - "skipContentsPublishing": false, - "skipBz2Publishing": false, - "FileSystemPublishEndpoints": {}, - "S3PublishEndpoints": {}, - "SwiftPublishEndpoints": {}, - "AzurePublishEndpoints": {}, - "AsyncAPI": false, - "enableMetricsEndpoint": false -} \ No newline at end of file +# Aptly Configuration File +########################### +# vim: : filetype=yaml + +# aptly 1.6.0 supports yaml configuraiton files with inline documentation and examples. +# Legacy json config files are still supported, and may be converted to yaml with `aptly config show -yaml` + +# Root directory for: +# - downloaded packages (`rootDir`/pool) +# - database (`rootDir`/db) +# - published repositories (`rootDir`/public) +root_dir: ~/.aptly + +# Log Level +# * debug +# * info +# * warning +# * error +log_level: info + +# Log Format +# * default (text) +# * json +log_format: default + +# Number of attempts to open database if it's locked by other instance +# * -1 (no retry) +database_open_attempts: -1 + +# Default Architectures +# empty list defaults to all available architectures +architectures: +# - amd64 + +# 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; +# if option is enabled, aptly stops checking for legacy paths; +# by default option is enabled for new aptly installations and disabled when +# upgrading from older versions +skip_legacy_pool: true + + +# Dependency following +####################### + +# Follow contents of `Suggests:` field when processing dependencies for the package +dep_follow_suggests: false + +# Follow contents of `Recommends:` field when processing dependencies for the package +dep_follow_recommends: false + +# When dependency looks like `package-a | package-b`, follow both variants always +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_verbose_resolve: false + + +# PPA +###### + +# Specify paramaters for short PPA url expansion +# empty defaults to output of `lsb_release` command +ppa_distributor_id: ubuntu + +# Codename for short PPA url expansion +ppa_codename: "" + + +# Aptly Server +############### + +# Serve published repos as well as API +serve_in_api_mode: false + +# Enable metrics for Prometheus client +enable_metrics_endpoint: false + +# Enable API documentation on /docs +enable_swagger_endpoint: false + +# OBSOLETE: use via url param ?_async=true +async_api: false + + +# Database +########### + +# Database backend +# Type must be one of: +# * leveldb (default) +# * etcd +database_backend: + type: leveldb + # Path to leveldb files + # empty dbPath defaults to `rootDir`/db + db_path: "" + + # type: etcd + # # URL to db server + # url: "127.0.0.1:2379" + + +# Mirroring +############ + +# Downloader +# * "default" +# * "grab" (more robust) +downloader: default + +# Number of parallel download threads to use when downloading packages +download_concurrency: 4 + +# Limit in kbytes/sec on download speed while mirroring remote repositories +download_limit: 0 + +# Number of retries for download attempts +download_retries: 0 + +# Download source packages per default +download_sourcepackages: false + + +# Signing +########## + +# GPG Provider +# * "internal" (Go internal implementation) +# * "gpg" (External `gpg` utility) +gpg_provider: gpg + +# Disable signing of published repositories +gpg_disable_sign: false + +# Disable signature verification of remote repositories +gpg_disable_verify: false + + +# Publishing +############# + +# Do not publish Contents files +skip_contents_publishing: false + +# Do not create bz2 files +skip_bz2_publishing: false + + +# Storage +########## + +# Filesystem publishing endpoints +# +# aptly defaults to publish to a single publish directory under `rootDir`/public. For +# a more advanced publishing strategy, you can define one or more filesystem endpoints in the +# `FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name +# and the following associated settings. +# +# In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name` +# with `endpoint-name` as the name given in the aptly configuration file. For example: +# +# `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily` +# +filesystem_publish_endpoints: + # # Endpoint Name + # test1: + # # Directory for publishing + # root_dir: /opt/srv/aptly_public + # # File Link Method for linking files from the internal pool to the published directory + # # * hardlink + # # * symlink + # # * copy + # link_method: hardlink + # # File Copare Method for comparing existing links from the internal pool to the published directory + # # Only used when "linkMethod" is set to "copy" + # # * md5 (default: compare md5 sum) + # # * 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:` +# +s3_publish_endpoints: + # # Endpoint Name + # test: + # # Amazon region for S3 bucket + # region: us-east-1 + # # Bucket name + # bucket: test-bucket + # # Prefix (optional) + # # publishing under specified prefix in the bucket, defaults to + # # no prefix (bucket root) + # prefix: "" + # # Default ACLs (optional) + # # assign ACL to published files: + # # * private (default, for use with apt S3 transport) + # # * public-read (public repository) + # # * none (don't set ACL) + # acl: private + # # Credentials (optional) + # # Amazon credentials to access S3 bucket. If not supplied, environment variables + # # `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` are used + # access_key_id: "" + # secret_access_key: "" + # session_token: "" + # # Endpoint (optional) + # # When using S3-compatible cloud storage, specify hostname of service endpoint here, + # # region is ignored if endpoint is set (set region to some human-readable name) + # # (should be left blank for real Amazon S3) + # endpoint: "" + # # Storage Class (optional) + # # Amazon S3 storage class, defaults to `STANDARD`. Other values + # # available: `REDUCED_REDUNDANCY` (lower price, lower redundancy) + # storage_class: STANDARD + # # Encryption Method (optional) + # # Server-side encryption method, defaults to none. Currently + # # the only available encryption method is `AES256` + # encryption_method: none + # # Plus Workaround (optional) + # # Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by + # # creating two copies of package files with `+` in filename: one original + # # and another one with spaces instead of plus signs + # # With `plusWorkaround` enabled, package files with plus sign + # # would be stored twice. aptly might not cleanup files with spaces when published + # # repository is dropped or updated (switched) to new version of repository (snapshot) + # plus_workaround: false + # # Disable MultiDel (optional) + # # For S3-compatible cloud storages which do not support `MultiDel` S3 API, + # # enable this setting (file deletion would be slower with this setting enabled) + # disable_multidel: false + # # Force Signature v2 (optional) + # # Disable Signature V4 support, useful with non-AWS S3-compatible object stores + # # which do not support SigV4, shouldn't be enabled for AWS + # force_sigv2: false + # # Force VirtualHosted Style (optional) + # # Disable path style visit, useful with non-AWS S3-compatible object stores + # # which only support virtual hosted style + # force_virtualhosted_style: false + # # Debug (optional) + # # Enables detailed request/response dump for each S3 operation + # debug: false + +# Swift Endpoint Support +# +# aptly can publish a repository directly to OpenStack Swift. +# Each endpoint has name and associated settings. +# +# In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before +# publishing prefix on the command line, e.g.: +# +# `aptly publish snapshot jessie-main swift:test:` +# +swift_publish_endpoints: + # # Endpoint Name + # test: + # # Container Name + # container: taylor1 + # # Prefix (optional) + # # Publish under specified prefix in the container, defaults to no prefix (container root) + # prefix: "" + # # Credentials (optional) + # # OpenStack credentials to access Keystone. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used + # username: "" + # password: "" + # # Domain (optional) + # # OpenStack domain + # domain: "" + # domain_id: "" + # # Tenant (optional) + # # OpenStack tenant (in order to use v2 authentication) + # tenant: "" + # tenant_id: "" + # tenant_domain: "" + # tenant_domain_id: "" + # # Auth URL (optional) + # # Full url of Keystone server (including port, and version). + # # Example `http://identity.example.com:5000/v2.0` + # auth_url: "" + +# Azure Endpoint Support +# +# aptly can be configured to publish repositories directly to Microsoft Azure Blob +# Storage. First, publishing endpoints should be described in the aptly +# configuration file. Each endpoint has its name and associated settings. +azure_publish_endpoints: + # # Endpoint Name + # test: + # # Container Name + # container: container1 + # # Prefix (optional) + # # Publishing under specified prefix in the container, defaults to no prefix (container root) + # prefix: "" + # # Credentials + # # Azure storage account access key to access blob storage + # account_name: "" + # account_key: "" + # # Endpoint URL + # # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string + # # defaults to "https://.blob.core.windows.net" + # endpoint: "" + +# Package Pool +# +# Location for storing downloaded packages +# Type must be one of: +# * local +# * azure +packagepool_storage: + # Local Pool + type: local + # Local Pool Path + # empty path defaults to `rootDir`/pool + path: + + # # Azure Azure Blob Storage Pool + # type: azure + # # Container Name + # container: pool1 + # # Prefix (optional) + # # Publishing under specified prefix in the container, defaults to no prefix (container root) + # prefix: "" + # # Credentials + # # Azure storage account access key to access blob storage + # account_name: "" + # account_key: "" + # # Endpoint URL + # # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string + # # defaults to "https://.blob.core.windows.net" + # endpoint: "" + diff --git a/system/t02_config/__init__.py b/system/t02_config/__init__.py index 240e5944..a5b0d2f4 100644 --- a/system/t02_config/__init__.py +++ b/system/t02_config/__init__.py @@ -1,66 +1,3 @@ """ Test config file """ - -import os -import re -import inspect -from lib import BaseTest - - -class CreateConfigTest(BaseTest): - """ - new file is generated if missing - """ - runCmd = "aptly mirror list" - checkedFile = os.path.join(os.environ["HOME"], ".aptly.conf") - - check = BaseTest.check_file - gold_processor = BaseTest.expand_environ - prepare = BaseTest.prepare_remove_all - - -class BadConfigTest(BaseTest): - """ - broken config file - """ - runCmd = "aptly mirror list" - expectedCode = 1 - - gold_processor = BaseTest.expand_environ - - def prepare(self): - self.prepare_remove_all() - - f = open(os.path.join(os.environ["HOME"], ".aptly.conf"), "w") - f.write("{some crap") - f.close() - - -class ConfigInFileTest(BaseTest): - """ - config in other file test - """ - runCmd = ["aptly", "mirror", "list", - "-config=%s" % (os.path.join(os.path.dirname(inspect.getsourcefile(BadConfigTest)), "aptly.conf"), )] - prepare = BaseTest.prepare_remove_all - - def outputMatchPrepare(_, s): - return re.sub(r' -(cpuprofile|memprofile|memstats|meminterval)=.*\n', '', s, flags=re.MULTILINE) - - -class ConfigInMissingFileTest(BaseTest): - """ - config in other file test - """ - runCmd = ["aptly", "mirror", "list", "-config=nosuchfile.conf"] - expectedCode = 1 - prepare = BaseTest.prepare_remove_all - - -class ConfigShowTest(BaseTest): - """ - config showing - """ - runCmd = ["aptly", "config", "show"] - gold_processor = BaseTest.expand_environ diff --git a/system/t02_config/config.py b/system/t02_config/config.py new file mode 100644 index 00000000..4bee72d8 --- /dev/null +++ b/system/t02_config/config.py @@ -0,0 +1,74 @@ +""" +Test config file +""" + +import os +import re +import inspect +from lib import BaseTest + + +class CreateConfigTest(BaseTest): + """ + new file is generated if missing + """ + runCmd = "aptly mirror list" + checkedFile = os.path.join(os.environ["HOME"], ".aptly.conf") + + check = BaseTest.check_file + gold_processor = BaseTest.expand_environ + prepare = BaseTest.prepare_remove_all + + +class BadConfigTest(BaseTest): + """ + broken config file + """ + runCmd = "aptly mirror list" + expectedCode = 1 + + gold_processor = BaseTest.expand_environ + + def prepare(self): + self.prepare_remove_all() + + f = open(os.path.join(os.environ["HOME"], ".aptly.conf"), "w") + f.write("{some crap") + f.close() + + +class ConfigInFileTest(BaseTest): + """ + config in other file test + """ + runCmd = ["aptly", "mirror", "list", + "-config=%s" % (os.path.join(os.path.dirname(inspect.getsourcefile(BadConfigTest)), "aptly.conf"), )] + prepare = BaseTest.prepare_remove_all + + def outputMatchPrepare(_, s): + return re.sub(r' -(cpuprofile|memprofile|memstats|meminterval)=.*\n', '', s, flags=re.MULTILINE) + + +class ConfigInMissingFileTest(BaseTest): + """ + config in other file test + """ + runCmd = ["aptly", "mirror", "list", "-config=nosuchfile.conf"] + expectedCode = 1 + prepare = BaseTest.prepare_remove_all + + +class ConfigShowTest(BaseTest): + """ + config showing + """ + runCmd = ["aptly", "config", "show"] + gold_processor = BaseTest.expand_environ + + +class ConfigShowYAMLTest(BaseTest): + """ + config showing + """ + runCmd = ["aptly", "config", "show", "-yaml"] + gold_processor = BaseTest.expand_environ diff --git a/system/t03_help/MainHelpTest_gold b/system/t03_help/MainHelpTest_gold index 004f140e..92f07618 100644 --- a/system/t03_help/MainHelpTest_gold +++ b/system/t03_help/MainHelpTest_gold @@ -12,7 +12,7 @@ package environment to new version. Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends diff --git a/system/t03_help/MainTest_gold b/system/t03_help/MainTest_gold index 5452f3f1..949288c5 100644 --- a/system/t03_help/MainTest_gold +++ b/system/t03_help/MainTest_gold @@ -20,7 +20,7 @@ Use "aptly help " for more information about a command. Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends diff --git a/system/t03_help/MirrorCreateHelpTest_gold b/system/t03_help/MirrorCreateHelpTest_gold index b266c7e0..c0cc3886 100644 --- a/system/t03_help/MirrorCreateHelpTest_gold +++ b/system/t03_help/MirrorCreateHelpTest_gold @@ -14,14 +14,14 @@ Example: Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends -dep-follow-source: when processing dependencies, follow from binary to Source packages -dep-follow-suggests: when processing dependencies, follow Suggests -dep-verbose-resolve: when processing dependencies, print detailed logs - -filter="": filter packages in mirror + -filter=: filter packages in mirror, use '@file' to read filter from file or '@-' for stdin -filter-with-deps: when filtering, include dependencies of matching packages as well -force-architectures: (only with architecture list) skip check that requested architectures are listed in Release file -force-components: (only with component list) skip check that requested components are listed in Release file diff --git a/system/t03_help/MirrorCreateTest_gold b/system/t03_help/MirrorCreateTest_gold index 8783fa5e..30bcfd86 100644 --- a/system/t03_help/MirrorCreateTest_gold +++ b/system/t03_help/MirrorCreateTest_gold @@ -5,14 +5,14 @@ aptly mirror create - create new mirror Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends -dep-follow-source: when processing dependencies, follow from binary to Source packages -dep-follow-suggests: when processing dependencies, follow Suggests -dep-verbose-resolve: when processing dependencies, print detailed logs - -filter="": filter packages in mirror + -filter=: filter packages in mirror, use '@file' to read filter from file or '@-' for stdin -filter-with-deps: when filtering, include dependencies of matching packages as well -force-architectures: (only with architecture list) skip check that requested architectures are listed in Release file -force-components: (only with component list) skip check that requested components are listed in Release file diff --git a/system/t03_help/MirrorHelpTest_gold b/system/t03_help/MirrorHelpTest_gold index 08f6f55a..4c105bf0 100644 --- a/system/t03_help/MirrorHelpTest_gold +++ b/system/t03_help/MirrorHelpTest_gold @@ -16,7 +16,7 @@ Use "mirror help " for more information about a command. Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends diff --git a/system/t03_help/MirrorTest_gold b/system/t03_help/MirrorTest_gold index e4825104..ae03d19f 100644 --- a/system/t03_help/MirrorTest_gold +++ b/system/t03_help/MirrorTest_gold @@ -16,7 +16,7 @@ Use "mirror help " for more information about a command. Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends diff --git a/system/t03_help/WrongFlagTest_gold b/system/t03_help/WrongFlagTest_gold index 3b293aaf..fb928b1e 100644 --- a/system/t03_help/WrongFlagTest_gold +++ b/system/t03_help/WrongFlagTest_gold @@ -6,14 +6,14 @@ aptly mirror create - create new mirror Options: -architectures="": list of architectures to consider during (comma-separated), default to all available - -config="": location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf) + -config="": location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf) -db-open-attempts=10: number of attempts to open DB if it's locked by other instance -dep-follow-all-variants: when processing dependencies, follow a & b if dependency is 'a|b' -dep-follow-recommends: when processing dependencies, follow Recommends -dep-follow-source: when processing dependencies, follow from binary to Source packages -dep-follow-suggests: when processing dependencies, follow Suggests -dep-verbose-resolve: when processing dependencies, print detailed logs - -filter="": filter packages in mirror + -filter=: filter packages in mirror, use '@file' to read filter from file or '@-' for stdin -filter-with-deps: when filtering, include dependencies of matching packages as well -force-architectures: (only with architecture list) skip check that requested architectures are listed in Release file -force-components: (only with component list) skip check that requested components are listed in Release file diff --git a/system/t03_help/__init__.py b/system/t03_help/__init__.py index 07f1f828..bfd292b1 100644 --- a/system/t03_help/__init__.py +++ b/system/t03_help/__init__.py @@ -1,65 +1,3 @@ """ Test help screens """ - -import re -from lib import BaseTest - - -class MainTest(BaseTest): - """ - main - """ - expectedCode = 2 - runCmd = "aptly" - - def outputMatchPrepare(_, s): - return re.sub(r' -(cpuprofile|memprofile|memstats|meminterval)=.*\n', '', s, flags=re.MULTILINE) - - -class MirrorTest(BaseTest): - """ - main - """ - expectedCode = 2 - runCmd = "aptly mirror" - - -class MirrorCreateTest(BaseTest): - """ - main - """ - expectedCode = 2 - runCmd = "aptly mirror create" - - -class MainHelpTest(BaseTest): - """ - main - """ - runCmd = "aptly help" - - def outputMatchPrepare(_, s): - return re.sub(r' -(cpuprofile|memprofile|memstats|meminterval)=.*\n', '', s, flags=re.MULTILINE) - - -class MirrorHelpTest(BaseTest): - """ - main - """ - runCmd = "aptly help mirror" - - -class MirrorCreateHelpTest(BaseTest): - """ - main - """ - runCmd = "aptly help mirror create" - - -class WrongFlagTest(BaseTest): - """ - main - """ - expectedCode = 2 - runCmd = "aptly mirror create -fxz=sss" diff --git a/system/t03_help/help.py b/system/t03_help/help.py new file mode 100644 index 00000000..07f1f828 --- /dev/null +++ b/system/t03_help/help.py @@ -0,0 +1,65 @@ +""" +Test help screens +""" + +import re +from lib import BaseTest + + +class MainTest(BaseTest): + """ + main + """ + expectedCode = 2 + runCmd = "aptly" + + def outputMatchPrepare(_, s): + return re.sub(r' -(cpuprofile|memprofile|memstats|meminterval)=.*\n', '', s, flags=re.MULTILINE) + + +class MirrorTest(BaseTest): + """ + main + """ + expectedCode = 2 + runCmd = "aptly mirror" + + +class MirrorCreateTest(BaseTest): + """ + main + """ + expectedCode = 2 + runCmd = "aptly mirror create" + + +class MainHelpTest(BaseTest): + """ + main + """ + runCmd = "aptly help" + + def outputMatchPrepare(_, s): + return re.sub(r' -(cpuprofile|memprofile|memstats|meminterval)=.*\n', '', s, flags=re.MULTILINE) + + +class MirrorHelpTest(BaseTest): + """ + main + """ + runCmd = "aptly help mirror" + + +class MirrorCreateHelpTest(BaseTest): + """ + main + """ + runCmd = "aptly help mirror create" + + +class WrongFlagTest(BaseTest): + """ + main + """ + expectedCode = 2 + runCmd = "aptly mirror create -fxz=sss" diff --git a/system/t04_mirror/CreateMirror10Test_gold b/system/t04_mirror/CreateMirror10Test_gold index 454968b1..d4644347 100644 --- a/system/t04_mirror/CreateMirror10Test_gold +++ b/system/t04_mirror/CreateMirror10Test_gold @@ -1,17 +1,18 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/InRelease... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch-backports/InRelease -gpgv: RSA key ID 22F3D138 +gpgv: Signature made Thu Mar 30 14:21:34 2023 UTC +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 -gpgv: RSA key ID 386FA1D9 +gpgv: Signature made Thu Mar 30 14:22:13 2023 UTC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/Release -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/Release.gpg... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch-backports/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch-backports/Release.gpg -gpgv: RSA key ID 22F3D138 +gpgv: Signature made Thu Mar 30 14:20:54 2023 UTC +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 -gpgv: RSA key ID 386FA1D9 +gpgv: Signature made Thu Mar 30 14:21:30 2023 UTC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 ERROR: unable to fetch mirror: verification of detached signature failed: exit status 2 diff --git a/system/t04_mirror/CreateMirror11Test_gold b/system/t04_mirror/CreateMirror11Test_gold index f225f436..2b5f5051 100644 --- a/system/t04_mirror/CreateMirror11Test_gold +++ b/system/t04_mirror/CreateMirror11Test_gold @@ -1,19 +1,22 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg -gpgv: RSA key ID B7D453EC +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " -gpgv: RSA key ID 22F3D138 +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " -gpgv: RSA key ID 386FA1D9 +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " -gpgv: RSA key ID 1A7B6500 +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: issuer "debian-release@lists.debian.org" gpgv: Good signature from "Debian Stable Release Key (9/stretch) " -Mirror [mirror11]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror11]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror11' to download repository contents. diff --git a/system/t04_mirror/CreateMirror11Test_mirror_show b/system/t04_mirror/CreateMirror11Test_mirror_show index 2d3d2dce..b2c40eb6 100644 --- a/system/t04_mirror/CreateMirror11Test_mirror_show +++ b/system/t04_mirror/CreateMirror11Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror11 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x diff --git a/system/t04_mirror/CreateMirror12Test_gold b/system/t04_mirror/CreateMirror12Test_gold index b6f86cb4..2bd20ad1 100644 --- a/system/t04_mirror/CreateMirror12Test_gold +++ b/system/t04_mirror/CreateMirror12Test_gold @@ -1,18 +1,21 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg -gpgv: RSA key ID B7D453EC +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC -gpgv: RSA key ID 22F3D138 +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 -gpgv: RSA key ID 386FA1D9 +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 -gpgv: RSA key ID 1A7B6500 +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: issuer "debian-release@lists.debian.org" ERROR: unable to fetch mirror: verification of detached signature failed: exit status 2 diff --git a/system/t04_mirror/CreateMirror13Test_gold b/system/t04_mirror/CreateMirror13Test_gold index e3af17b0..f630f384 100644 --- a/system/t04_mirror/CreateMirror13Test_gold +++ b/system/t04_mirror/CreateMirror13Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror13]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror13]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror13' to download repository contents. diff --git a/system/t04_mirror/CreateMirror13Test_mirror_show b/system/t04_mirror/CreateMirror13Test_mirror_show index fb0116a4..61e380fd 100644 --- a/system/t04_mirror/CreateMirror13Test_mirror_show +++ b/system/t04_mirror/CreateMirror13Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror13 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x diff --git a/system/t04_mirror/CreateMirror14Test_gold b/system/t04_mirror/CreateMirror14Test_gold index 2ca76b61..6ba64199 100644 --- a/system/t04_mirror/CreateMirror14Test_gold +++ b/system/t04_mirror/CreateMirror14Test_gold @@ -1,8 +1,7 @@ -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease... -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease -gpgv: RSA key ID 115C3D8A -gpgv: Good signature from "Johannes Ranke (Wissenschaftlicher Berater) " -gpgv: aka "Johannes Ranke " +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/InRelease +gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC +gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 +gpgv: Good signature from "Johannes Ranke " -Mirror [mirror14]: https://cloud.r-project.org/bin/linux/debian/ ./jessie-cran35/ successfully added. +Mirror [mirror14]: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/ ./bullseye-cran40/ successfully added. You can run 'aptly mirror update mirror14' to download repository contents. diff --git a/system/t04_mirror/CreateMirror14Test_mirror_show b/system/t04_mirror/CreateMirror14Test_mirror_show index 3309f75d..38056b54 100644 --- a/system/t04_mirror/CreateMirror14Test_mirror_show +++ b/system/t04_mirror/CreateMirror14Test_mirror_show @@ -1,16 +1,16 @@ Name: mirror14 -Archive Root URL: https://cloud.r-project.org/bin/linux/debian/ -Distribution: ./jessie-cran35/ +Archive Root URL: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/ +Distribution: ./bullseye-cran40/ Components: -Architectures: +Architectures: all, amd64, i386 Download Sources: no Download .udebs: no Last update: never Information from release file: -Architectures: i386 amd64 armel all -Codename: jessie-cran35 +Architectures: i386 amd64 all +Codename: bullseye-cran40 Components: main Label: CRAN Backports Origin: CRAN -Suite: jessie-cran35 +Suite: bullseye-cran40 diff --git a/system/t04_mirror/CreateMirror16Test_gold b/system/t04_mirror/CreateMirror16Test_gold index 8badf778..4511ddd9 100644 --- a/system/t04_mirror/CreateMirror16Test_gold +++ b/system/t04_mirror/CreateMirror16Test_gold @@ -1,3 +1,2 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -ERROR: unable to fetch mirror: architecture source not available in repo [mirror16]: http://cdn-fastly.deb.debian.org/debian/ stretch, use -force-architectures to override +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +ERROR: unable to fetch mirror: architecture source not available in repo [mirror16]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch, use -force-architectures to override diff --git a/system/t04_mirror/CreateMirror17Test_gold b/system/t04_mirror/CreateMirror17Test_gold index d552ada6..b5866902 100644 --- a/system/t04_mirror/CreateMirror17Test_gold +++ b/system/t04_mirror/CreateMirror17Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror17]: http://cdn-fastly.deb.debian.org/debian/ stretch [src] successfully added. +Mirror [mirror17]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch [src] successfully added. You can run 'aptly mirror update mirror17' to download repository contents. diff --git a/system/t04_mirror/CreateMirror17Test_mirror_show b/system/t04_mirror/CreateMirror17Test_mirror_show index cd9eec87..5f233f29 100644 --- a/system/t04_mirror/CreateMirror17Test_mirror_show +++ b/system/t04_mirror/CreateMirror17Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror17 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: i386 diff --git a/system/t04_mirror/CreateMirror18Test_gold b/system/t04_mirror/CreateMirror18Test_gold index 7367a3fc..d63b8745 100644 --- a/system/t04_mirror/CreateMirror18Test_gold +++ b/system/t04_mirror/CreateMirror18Test_gold @@ -1,12 +1,9 @@ -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... -Error downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease: HTTP code 404 while fetching http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease retrying... -Retrying 0 http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... -Giving up on http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release.gpg... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release.gpg -gpgv: RSA key ID 3B1F56C0 +Downloading: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease +gpgv: Signature made Sun Jul 28 07:57:01 2024 UTC +gpgv: using RSA key 5BFCD481D86D5824470E469F9000B1C3A01F726C +gpgv: Good signature from "Launchpad PPA for Anton Gladky" +gpgv: Signature made Sun Jul 28 07:57:01 2024 UTC +gpgv: using RSA key 02219381E9161C78A46CB2BFA5279A973B1F56C0 gpgv: Good signature from "Launchpad sim" Mirror [mirror18]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick successfully added. diff --git a/system/t04_mirror/CreateMirror18Test_mirror_show b/system/t04_mirror/CreateMirror18Test_mirror_show index d2d32be5..750161b0 100644 --- a/system/t04_mirror/CreateMirror18Test_mirror_show +++ b/system/t04_mirror/CreateMirror18Test_mirror_show @@ -11,7 +11,7 @@ Information from release file: Architectures: amd64 armel i386 powerpc Codename: maverick Components: main -Date: Mon, 22 Oct 2012 13:19:50 UTC +Date: Sun, 28 Jul 2024 7:57:00 UTC Description: Ubuntu Maverick 10.10 Label: gnuplot diff --git a/system/t04_mirror/CreateMirror19Test_gold b/system/t04_mirror/CreateMirror19Test_gold index 97eae908..4ae30b74 100644 --- a/system/t04_mirror/CreateMirror19Test_gold +++ b/system/t04_mirror/CreateMirror19Test_gold @@ -1,9 +1,10 @@ -Downloading http://security.debian.org/dists/stretch/updates/InRelease... -Success downloading http://security.debian.org/dists/stretch/updates/InRelease -gpgv: RSA key ID 331F7F50 +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease +gpgv: Signature made Sat Feb 18 04:22:45 2023 UTC +gpgv: using RSA key 379483D8B60160B155B372DDAA8E81B4331F7F50 gpgv: Good signature from "Debian Security Archive Automatic Signing Key (9/stretch) " -gpgv: RSA key ID E562B32A +gpgv: Signature made Sat Feb 18 04:22:45 2023 UTC +gpgv: using RSA key 5237CEEEF212F3D51C74ABE0112695A0E562B32A gpgv: Good signature from "Debian Security Archive Automatic Signing Key (10/buster) " -Mirror [mirror19]: http://security.debian.org/ stretch/updates [src] successfully added. +Mirror [mirror19]: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates [src] successfully added. You can run 'aptly mirror update mirror19' to download repository contents. diff --git a/system/t04_mirror/CreateMirror19Test_mirror_show b/system/t04_mirror/CreateMirror19Test_mirror_show index 4a1f27e9..5bcaf744 100644 --- a/system/t04_mirror/CreateMirror19Test_mirror_show +++ b/system/t04_mirror/CreateMirror19Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror19 -Archive Root URL: http://security.debian.org/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ Distribution: stretch/updates Components: main Architectures: i386 diff --git a/system/t04_mirror/CreateMirror1Test_gold b/system/t04_mirror/CreateMirror1Test_gold index a14855d8..6a9d12ed 100644 --- a/system/t04_mirror/CreateMirror1Test_gold +++ b/system/t04_mirror/CreateMirror1Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror1]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror1]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror1' to download repository contents. diff --git a/system/t04_mirror/CreateMirror1Test_mirror_show b/system/t04_mirror/CreateMirror1Test_mirror_show index 44e12aec..3acf9b5b 100644 --- a/system/t04_mirror/CreateMirror1Test_mirror_show +++ b/system/t04_mirror/CreateMirror1Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror1 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x diff --git a/system/t04_mirror/CreateMirror20Test_gold b/system/t04_mirror/CreateMirror20Test_gold index 0099d586..2beb4d39 100644 --- a/system/t04_mirror/CreateMirror20Test_gold +++ b/system/t04_mirror/CreateMirror20Test_gold @@ -1,9 +1,9 @@ -Downloading http://security.debian.org/dists/stretch/updates/InRelease... -Error downloading http://security.debian.org/dists/stretch/updates/InRelease: http://security.debian.org/dists/stretch/updates/InRelease: Get "http://security.debian.org/dists/stretch/updates/InRelease": http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused retrying... -Retrying 0 http://security.debian.org/dists/stretch/updates/InRelease... -Giving up on http://security.debian.org/dists/stretch/updates/InRelease... -Downloading http://security.debian.org/dists/stretch/updates/Release... -Error downloading http://security.debian.org/dists/stretch/updates/Release: http://security.debian.org/dists/stretch/updates/Release: Get "http://security.debian.org/dists/stretch/updates/Release": http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused retrying... -Retrying 0 http://security.debian.org/dists/stretch/updates/Release... -Giving up on http://security.debian.org/dists/stretch/updates/Release... -ERROR: unable to fetch mirror: http://security.debian.org/dists/stretch/updates/Release: Get http://security.debian.org/dists/stretch/updates/Release: http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease +Error (retrying): http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease: Get "http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease": http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release +Error (retrying): http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release: Get "http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release": http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release +ERROR: unable to fetch mirror: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release: Get "http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release": http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused diff --git a/system/t04_mirror/CreateMirror21Test_gold b/system/t04_mirror/CreateMirror21Test_gold index de4be269..f53413fc 100644 --- a/system/t04_mirror/CreateMirror21Test_gold +++ b/system/t04_mirror/CreateMirror21Test_gold @@ -1,13 +1,12 @@ -Downloading http://pkg.jenkins-ci.org/debian-stable/binary/InRelease... -Error downloading http://pkg.jenkins-ci.org/debian-stable/binary/InRelease: HTTP code 404 while fetching http://pkg.jenkins-ci.org/debian-stable/binary/InRelease retrying... -Retrying 0 http://pkg.jenkins-ci.org/debian-stable/binary/InRelease... -Giving up on http://pkg.jenkins-ci.org/debian-stable/binary/InRelease... -Downloading http://pkg.jenkins-ci.org/debian-stable/binary/Release... -Success downloading http://pkg.jenkins-ci.org/debian-stable/binary/Release -Downloading http://pkg.jenkins-ci.org/debian-stable/binary/Release.gpg... -Success downloading http://pkg.jenkins-ci.org/debian-stable/binary/Release.gpg -gpgv: RSA key ID 45F2C3D5 +Downloading: http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/binary/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/binary/InRelease +Retrying 0 http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/binary/InRelease... +Download Error: http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/binary/InRelease +Downloading: http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/binary/Release +Downloading: http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/binary/Release.gpg +gpgv: Signature made Wed Dec 13 17:19:55 2023 UTC +gpgv: using RSA key 63667EE74BBA1F0A08A698725BA31D57EF5975CA gpgv: Good signature from "Jenkins Project " -Mirror [mirror21]: http://pkg.jenkins-ci.org/debian-stable/ ./binary/ successfully added. +Mirror [mirror21]: http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/ ./binary/ successfully added. You can run 'aptly mirror update mirror21' to download repository contents. diff --git a/system/t04_mirror/CreateMirror21Test_mirror_show b/system/t04_mirror/CreateMirror21Test_mirror_show index ba5365ac..2eed39be 100644 --- a/system/t04_mirror/CreateMirror21Test_mirror_show +++ b/system/t04_mirror/CreateMirror21Test_mirror_show @@ -1,14 +1,13 @@ Name: mirror21 -Archive Root URL: http://pkg.jenkins-ci.org/debian-stable/ +Archive Root URL: http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable/ Distribution: ./binary/ Components: -Architectures: +Architectures: all Download Sources: no Download .udebs: no Last update: never Information from release file: Architectures: all -Date: Wed, 28 Jan 2015 02:32:16 UTC Origin: jenkins.io Suite: binary diff --git a/system/t04_mirror/CreateMirror22Test_gold b/system/t04_mirror/CreateMirror22Test_gold index dc74de6a..1c6143a6 100644 --- a/system/t04_mirror/CreateMirror22Test_gold +++ b/system/t04_mirror/CreateMirror22Test_gold @@ -1,5 +1,4 @@ -Downloading http://security.debian.org/dists/stretch/updates/Release... -Success downloading http://security.debian.org/dists/stretch/updates/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release -Mirror [mirror22]: http://security.debian.org/ stretch/updates successfully added. +Mirror [mirror22]: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates successfully added. You can run 'aptly mirror update mirror22' to download repository contents. diff --git a/system/t04_mirror/CreateMirror22Test_mirror_show b/system/t04_mirror/CreateMirror22Test_mirror_show index 54e7268b..2e8ddb9d 100644 --- a/system/t04_mirror/CreateMirror22Test_mirror_show +++ b/system/t04_mirror/CreateMirror22Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror22 -Archive Root URL: http://security.debian.org/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ Distribution: stretch/updates Components: main Architectures: amd64, arm64, armel, armhf, i386 diff --git a/system/t04_mirror/CreateMirror24Test_gold b/system/t04_mirror/CreateMirror24Test_gold index 4ca1af86..4986d89b 100644 --- a/system/t04_mirror/CreateMirror24Test_gold +++ b/system/t04_mirror/CreateMirror24Test_gold @@ -1,9 +1,10 @@ -Downloading http://security.debian.org/dists/stretch/updates/InRelease... -Success downloading http://security.debian.org/dists/stretch/updates/InRelease -gpgv: RSA key ID 331F7F50 +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease +gpgv: Signature made Sat Feb 18 04:22:45 2023 UTC +gpgv: using RSA key 379483D8B60160B155B372DDAA8E81B4331F7F50 gpgv: Good signature from "Debian Security Archive Automatic Signing Key (9/stretch) " -gpgv: RSA key ID E562B32A +gpgv: Signature made Sat Feb 18 04:22:45 2023 UTC +gpgv: using RSA key 5237CEEEF212F3D51C74ABE0112695A0E562B32A gpgv: Good signature from "Debian Security Archive Automatic Signing Key (10/buster) " -Mirror [mirror24]: http://security.debian.org/ stretch/updates successfully added. +Mirror [mirror24]: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates successfully added. You can run 'aptly mirror update mirror24' to download repository contents. diff --git a/system/t04_mirror/CreateMirror25Test_gold b/system/t04_mirror/CreateMirror25Test_gold index 511269d4..fb4354c4 100644 --- a/system/t04_mirror/CreateMirror25Test_gold +++ b/system/t04_mirror/CreateMirror25Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror25]: http://cdn-fastly.deb.debian.org/debian/ stretch [udeb] successfully added. +Mirror [mirror25]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch [udeb] successfully added. You can run 'aptly mirror update mirror25' to download repository contents. diff --git a/system/t04_mirror/CreateMirror25Test_mirror_show b/system/t04_mirror/CreateMirror25Test_mirror_show index bf0723ff..0b2c675e 100644 --- a/system/t04_mirror/CreateMirror25Test_mirror_show +++ b/system/t04_mirror/CreateMirror25Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror25 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: i386 diff --git a/system/t04_mirror/CreateMirror27Test_gold b/system/t04_mirror/CreateMirror27Test_gold index 7f76508f..0b5009fb 100644 --- a/system/t04_mirror/CreateMirror27Test_gold +++ b/system/t04_mirror/CreateMirror27Test_gold @@ -1,6 +1,4 @@ -Downloading https://mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/dists/wheezy/Release... -Following redirect to https://linux.dell.com/repo/community/ubuntu/dists/wheezy/Release... -Success downloading https://mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/dists/wheezy/Release +Downloading: http://repo.aptly.info/system-tests/mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/dists/wheezy/Release -Mirror [mirror27]: https://mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/ wheezy successfully added. +Mirror [mirror27]: http://repo.aptly.info/system-tests/mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/ wheezy successfully added. You can run 'aptly mirror update mirror27' to download repository contents. diff --git a/system/t04_mirror/CreateMirror27Test_mirror_show b/system/t04_mirror/CreateMirror27Test_mirror_show index fd272187..97f56255 100644 --- a/system/t04_mirror/CreateMirror27Test_mirror_show +++ b/system/t04_mirror/CreateMirror27Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror27 -Archive Root URL: https://mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/ +Archive Root URL: http://repo.aptly.info/system-tests/mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu/ Distribution: wheezy Components: openmanage/740 Architectures: amd64, i386 diff --git a/system/t04_mirror/CreateMirror29Test_gold b/system/t04_mirror/CreateMirror29Test_gold index be15443b..a4e8c3c3 100644 --- a/system/t04_mirror/CreateMirror29Test_gold +++ b/system/t04_mirror/CreateMirror29Test_gold @@ -1,9 +1,8 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/InRelease... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch-backports/InRelease openpgp: RSA key ID 648ACFD622F3D138 openpgp: Good signature from "Debian Archive Automatic Signing Key (10/buster) " openpgp: RSA key ID 0E98404D386FA1D9 openpgp: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " -Mirror [mirror9]: http://cdn-fastly.deb.debian.org/debian/ stretch-backports successfully added. +Mirror [mirror9]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch-backports successfully added. You can run 'aptly mirror update mirror9' to download repository contents. diff --git a/system/t04_mirror/CreateMirror2Test_gold b/system/t04_mirror/CreateMirror2Test_gold index 2f74f102..2fda5e6f 100644 --- a/system/t04_mirror/CreateMirror2Test_gold +++ b/system/t04_mirror/CreateMirror2Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror2]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror2]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror2' to download repository contents. diff --git a/system/t04_mirror/CreateMirror2Test_mirror_show b/system/t04_mirror/CreateMirror2Test_mirror_show index 8e898fc4..e17e9619 100644 --- a/system/t04_mirror/CreateMirror2Test_mirror_show +++ b/system/t04_mirror/CreateMirror2Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror2 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x diff --git a/system/t04_mirror/CreateMirror30Test_gold b/system/t04_mirror/CreateMirror30Test_gold index 7da5ef71..ae944b8e 100644 --- a/system/t04_mirror/CreateMirror30Test_gold +++ b/system/t04_mirror/CreateMirror30Test_gold @@ -1,14 +1,12 @@ opengpg: failure opening keyring '${HOME}/.gnupg/aptlytest.gpg': open ${HOME}/.gnupg/aptlytest.gpg: no such file or directory Looks like your keyring with trusted keys is empty. You might consider importing some keys. -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg openpgp: RSA key ID 04EE7237B7D453EC openpgp: Can't check signature: public key not found openpgp: RSA key ID 648ACFD622F3D138 diff --git a/system/t04_mirror/CreateMirror31Test_gold b/system/t04_mirror/CreateMirror31Test_gold index 8dc0bb89..6ea78d7a 100644 --- a/system/t04_mirror/CreateMirror31Test_gold +++ b/system/t04_mirror/CreateMirror31Test_gold @@ -1,11 +1,9 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg openpgp: RSA key ID 04EE7237B7D453EC openpgp: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " openpgp: RSA key ID 648ACFD622F3D138 @@ -15,5 +13,5 @@ openpgp: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) openpgp: RSA key ID EF0F382A1A7B6500 openpgp: Good signature from "Debian Stable Release Key (9/stretch) " -Mirror [mirror11]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror11]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror11' to download repository contents. diff --git a/system/t04_mirror/CreateMirror32Test_gold b/system/t04_mirror/CreateMirror32Test_gold index 95ca9062..74b4fd17 100644 --- a/system/t04_mirror/CreateMirror32Test_gold +++ b/system/t04_mirror/CreateMirror32Test_gold @@ -1,16 +1,14 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " gpgv: issuer "debian-release@lists.debian.org" gpgv: Good signature from "Debian Stable Release Key (9/stretch) " -Mirror [mirror32]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror32]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror32' to download repository contents. diff --git a/system/t04_mirror/CreateMirror32Test_mirror_show b/system/t04_mirror/CreateMirror32Test_mirror_show index 63fb46fd..6cd09c53 100644 --- a/system/t04_mirror/CreateMirror32Test_mirror_show +++ b/system/t04_mirror/CreateMirror32Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror32 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x diff --git a/system/t04_mirror/CreateMirror33Test_gold b/system/t04_mirror/CreateMirror33Test_gold new file mode 100644 index 00000000..5e363199 --- /dev/null +++ b/system/t04_mirror/CreateMirror33Test_gold @@ -0,0 +1,8 @@ +Downloading: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/Release +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/Release +Retrying 0 http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/Release... +Download Error: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/Release +Downloading: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/InRelease + +Mirror [mirror33]: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/ ./ successfully added. +You can run 'aptly mirror update mirror33' to download repository contents. diff --git a/system/t04_mirror/CreateMirror33Test_mirror_show b/system/t04_mirror/CreateMirror33Test_mirror_show new file mode 100644 index 00000000..24e24938 --- /dev/null +++ b/system/t04_mirror/CreateMirror33Test_mirror_show @@ -0,0 +1,20 @@ +Name: mirror33 +Archive Root URL: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/ +Distribution: ./ +Components: +Architectures: amd64 +Download Sources: no +Download .udebs: no +Last update: never + +Information from release file: +Architectures: amd64 +Codename: xenial +Components: main +Date: Wed, 27 Sep 2017 23:08:53 +0000 +Description: NVIDIA container runtime library repository + +Label: NVIDIA CORPORATION +Origin: https://nvidia.github.io/libnvidia-container +Suite: xenial +Version: 1.0 diff --git a/system/t04_mirror/CreateMirror34Test_gold b/system/t04_mirror/CreateMirror34Test_gold new file mode 100644 index 00000000..291d2058 --- /dev/null +++ b/system/t04_mirror/CreateMirror34Test_gold @@ -0,0 +1,6 @@ +Downloading: http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/Release +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/Release +Retrying 0 http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/Release... +Download Error: http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/Release +Downloading: http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/InRelease +ERROR: unable to fetch mirror: no architectures found, please specify diff --git a/system/t04_mirror/CreateMirror35Test_gold b/system/t04_mirror/CreateMirror35Test_gold new file mode 100644 index 00000000..ae8e1c6d --- /dev/null +++ b/system/t04_mirror/CreateMirror35Test_gold @@ -0,0 +1,6 @@ +Downloading & parsing package files... +Applying filter... +Packages filtered: 4743 -> 81. +Building download queue... +Download queue: 81 items (3.00 GiB) + diff --git a/system/t04_mirror/CreateMirror35Test_mirror_show b/system/t04_mirror/CreateMirror35Test_mirror_show new file mode 100644 index 00000000..3ae1920a --- /dev/null +++ b/system/t04_mirror/CreateMirror35Test_mirror_show @@ -0,0 +1,17 @@ +Name: mirror35 +Archive Root URL: http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ +Distribution: ./ +Components: +Architectures: amd64 +Download Sources: no +Download .udebs: no +Filter: cuda-12-6 (= 12.6.2-1) +Filter With Deps: yes +Last update: never + +Information from release file: +Acquire-By-Hash: no +Architecture: x86_64 +Date: Mon, 04 Nov 2024 16:02:23 +0000 +Label: NVIDIA CUDA +Origin: NVIDIA diff --git a/system/t04_mirror/CreateMirror36Test_gold b/system/t04_mirror/CreateMirror36Test_gold new file mode 100644 index 00000000..eb089d7e --- /dev/null +++ b/system/t04_mirror/CreateMirror36Test_gold @@ -0,0 +1,4 @@ +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release + +Mirror [mirror36]: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates successfully added. +You can run 'aptly mirror update mirror36' to download repository contents. diff --git a/system/t04_mirror/CreateMirror36Test_mirror_show b/system/t04_mirror/CreateMirror36Test_mirror_show new file mode 100644 index 00000000..c6188417 --- /dev/null +++ b/system/t04_mirror/CreateMirror36Test_mirror_show @@ -0,0 +1,22 @@ +Name: mirror36 +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ +Distribution: stretch/updates +Components: main +Architectures: amd64, arm64, armel, armhf, i386 +Download Sources: no +Download .udebs: no +Filter: nginx | Priority (required) +Filter With Deps: no +Last update: never + +Information from release file: +Acquire-By-Hash: yes +Architectures: amd64 arm64 armel armhf i386 +Codename: stretch +Components: updates/main updates/contrib updates/non-free +Description: Debian 9 Security Updates + +Label: Debian-Security +Origin: Debian +Suite: oldoldstable +Version: 9 diff --git a/system/t04_mirror/CreateMirror37Test_gold b/system/t04_mirror/CreateMirror37Test_gold new file mode 100644 index 00000000..ef68aabc --- /dev/null +++ b/system/t04_mirror/CreateMirror37Test_gold @@ -0,0 +1,4 @@ +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release + +Mirror [mirror37]: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates successfully added. +You can run 'aptly mirror update mirror37' to download repository contents. diff --git a/system/t04_mirror/CreateMirror37Test_mirror_show b/system/t04_mirror/CreateMirror37Test_mirror_show new file mode 100644 index 00000000..aa2750a2 --- /dev/null +++ b/system/t04_mirror/CreateMirror37Test_mirror_show @@ -0,0 +1,22 @@ +Name: mirror37 +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ +Distribution: stretch/updates +Components: main +Architectures: amd64, arm64, armel, armhf, i386 +Download Sources: no +Download .udebs: no +Filter: nginx | Priority (required) +Filter With Deps: no +Last update: never + +Information from release file: +Acquire-By-Hash: yes +Architectures: amd64 arm64 armel armhf i386 +Codename: stretch +Components: updates/main updates/contrib updates/non-free +Description: Debian 9 Security Updates + +Label: Debian-Security +Origin: Debian +Suite: oldoldstable +Version: 9 diff --git a/system/t04_mirror/CreateMirror3Test_gold b/system/t04_mirror/CreateMirror3Test_gold index 5fabc64f..8897cd5b 100644 --- a/system/t04_mirror/CreateMirror3Test_gold +++ b/system/t04_mirror/CreateMirror3Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror3]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror3]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror3' to download repository contents. diff --git a/system/t04_mirror/CreateMirror3Test_mirror_show b/system/t04_mirror/CreateMirror3Test_mirror_show index 520c6ea3..f016c6ef 100644 --- a/system/t04_mirror/CreateMirror3Test_mirror_show +++ b/system/t04_mirror/CreateMirror3Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror3 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib Architectures: i386, amd64 diff --git a/system/t04_mirror/CreateMirror4Test_gold b/system/t04_mirror/CreateMirror4Test_gold index 0824a5ea..49002f9d 100644 --- a/system/t04_mirror/CreateMirror4Test_gold +++ b/system/t04_mirror/CreateMirror4Test_gold @@ -1,3 +1,2 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -ERROR: unable to fetch mirror: component life not available in repo [mirror4]: http://cdn-fastly.deb.debian.org/debian/ stretch, use -force-components to override +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +ERROR: unable to fetch mirror: component life not available in repo [mirror4]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch, use -force-components to override diff --git a/system/t04_mirror/CreateMirror5Test_gold b/system/t04_mirror/CreateMirror5Test_gold index 3bbe6335..9cf18a4a 100644 --- a/system/t04_mirror/CreateMirror5Test_gold +++ b/system/t04_mirror/CreateMirror5Test_gold @@ -1,3 +1,2 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -ERROR: unable to fetch mirror: architecture nano68 not available in repo [mirror5]: http://cdn-fastly.deb.debian.org/debian/ stretch, use -force-architectures to override +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +ERROR: unable to fetch mirror: architecture nano68 not available in repo [mirror5]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch, use -force-architectures to override diff --git a/system/t04_mirror/CreateMirror6Test_gold b/system/t04_mirror/CreateMirror6Test_gold index 748dd862..956c93bc 100644 --- a/system/t04_mirror/CreateMirror6Test_gold +++ b/system/t04_mirror/CreateMirror6Test_gold @@ -1,9 +1,9 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/suslik/InRelease... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/suslik/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/suslik/InRelease retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/suslik/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/suslik/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/suslik/Release... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/suslik/Release: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/suslik/Release retrying... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/suslik/Release... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/suslik/Release... -ERROR: unable to fetch mirror: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/suslik/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/InRelease... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/Release +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/Release +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/Release... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/Release +ERROR: unable to fetch mirror: HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/suslik/Release diff --git a/system/t04_mirror/CreateMirror7Test_gold b/system/t04_mirror/CreateMirror7Test_gold index e527f84e..10bfc5ba 100644 --- a/system/t04_mirror/CreateMirror7Test_gold +++ b/system/t04_mirror/CreateMirror7Test_gold @@ -1,5 +1,4 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release -Mirror [mirror7]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully added. +Mirror [mirror7]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully added. You can run 'aptly mirror update mirror7' to download repository contents. diff --git a/system/t04_mirror/CreateMirror7Test_mirror_show b/system/t04_mirror/CreateMirror7Test_mirror_show index 4465c015..a62cfb67 100644 --- a/system/t04_mirror/CreateMirror7Test_mirror_show +++ b/system/t04_mirror/CreateMirror7Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror7 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib Architectures: i386, amd64 diff --git a/system/t04_mirror/CreateMirror8Test_gold b/system/t04_mirror/CreateMirror8Test_gold index 69138eca..e9b9017f 100644 --- a/system/t04_mirror/CreateMirror8Test_gold +++ b/system/t04_mirror/CreateMirror8Test_gold @@ -1,3 +1,2 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release ERROR: unable to add mirror: mirror with name mirror8 already exists diff --git a/system/t04_mirror/CreateMirror9Test_gold b/system/t04_mirror/CreateMirror9Test_gold index d3be9e6a..be1879e1 100644 --- a/system/t04_mirror/CreateMirror9Test_gold +++ b/system/t04_mirror/CreateMirror9Test_gold @@ -1,9 +1,10 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/InRelease... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch-backports/InRelease -gpgv: RSA key ID 22F3D138 +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch-backports/InRelease +gpgv: Signature made Thu Mar 30 14:21:34 2023 UTC +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " -gpgv: RSA key ID 386FA1D9 +gpgv: Signature made Thu Mar 30 14:22:13 2023 UTC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " -Mirror [mirror9]: http://cdn-fastly.deb.debian.org/debian/ stretch-backports successfully added. +Mirror [mirror9]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch-backports successfully added. You can run 'aptly mirror update mirror9' to download repository contents. diff --git a/system/t04_mirror/CreateMirror9Test_mirror_show b/system/t04_mirror/CreateMirror9Test_mirror_show index 37039266..302a565c 100644 --- a/system/t04_mirror/CreateMirror9Test_mirror_show +++ b/system/t04_mirror/CreateMirror9Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror9 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch-backports Components: main, contrib, non-free Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x @@ -8,7 +8,6 @@ Download .udebs: no Last update: never Information from release file: -Acquire-By-Hash: yes Architectures: amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64el s390x ButAutomaticUpgrades: yes Codename: stretch-backports diff --git a/system/t04_mirror/EditMirror10Test_gold b/system/t04_mirror/EditMirror10Test_gold index 563cdde8..71263eca 100644 --- a/system/t04_mirror/EditMirror10Test_gold +++ b/system/t04_mirror/EditMirror10Test_gold @@ -1,3 +1,2 @@ -Downloading ftp://ftp.ch.debian.org/debian/dists/stretch/Release... -Success downloading ftp://ftp.ch.debian.org/debian/dists/stretch/Release -Mirror [mirror10]: ftp://ftp.ch.debian.org/debian/ stretch successfully updated. +Downloading: http://repo.aptly.info/system-tests/ftp.ch.debian.org/debian/dists/bookworm/Release +Mirror [mirror10]: http://repo.aptly.info/system-tests/ftp.ch.debian.org/debian/ bookworm successfully updated. diff --git a/system/t04_mirror/EditMirror5Test_gold b/system/t04_mirror/EditMirror5Test_gold index 7b872740..23379081 100644 --- a/system/t04_mirror/EditMirror5Test_gold +++ b/system/t04_mirror/EditMirror5Test_gold @@ -1 +1 @@ -Mirror [mirror5]: http://security.debian.org/ stretch/updates successfully updated. +Mirror [mirror5]: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates successfully updated. diff --git a/system/t04_mirror/EditMirror5Test_mirror_show b/system/t04_mirror/EditMirror5Test_mirror_show index 685e8fff..c6dfb1fb 100644 --- a/system/t04_mirror/EditMirror5Test_mirror_show +++ b/system/t04_mirror/EditMirror5Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror5 -Archive Root URL: http://security.debian.org/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ Distribution: stretch/updates Components: main Architectures: amd64, arm64, armel, armhf, i386 diff --git a/system/t04_mirror/EditMirror6Test_gold b/system/t04_mirror/EditMirror6Test_gold index bc37c97b..8aaca4ef 100644 --- a/system/t04_mirror/EditMirror6Test_gold +++ b/system/t04_mirror/EditMirror6Test_gold @@ -1,3 +1,2 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Mirror [mirror6]: http://cdn-fastly.deb.debian.org/debian/ stretch successfully updated. +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Mirror [mirror6]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch successfully updated. diff --git a/system/t04_mirror/EditMirror6Test_mirror_show b/system/t04_mirror/EditMirror6Test_mirror_show index 2ff6c423..ac42dcfc 100644 --- a/system/t04_mirror/EditMirror6Test_mirror_show +++ b/system/t04_mirror/EditMirror6Test_mirror_show @@ -1,5 +1,5 @@ Name: mirror6 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main Architectures: amd64, i386 diff --git a/system/t04_mirror/EditMirror7Test_gold b/system/t04_mirror/EditMirror7Test_gold index 2c57a1b6..52e969c7 100644 --- a/system/t04_mirror/EditMirror7Test_gold +++ b/system/t04_mirror/EditMirror7Test_gold @@ -1,3 +1,2 @@ -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -ERROR: unable to edit: architecture x56 not available in repo [stretch]: http://cdn-fastly.deb.debian.org/debian/ stretch, use -force-architectures to override +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +ERROR: unable to edit: architecture x56 not available in repo [stretch]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch, use -force-architectures to override diff --git a/system/t04_mirror/ListMirror1Test_gold b/system/t04_mirror/ListMirror1Test_gold index f618902f..dff50d88 100644 --- a/system/t04_mirror/ListMirror1Test_gold +++ b/system/t04_mirror/ListMirror1Test_gold @@ -1,7 +1,7 @@ List of mirrors: - * [mirror1]: http://cdn-fastly.deb.debian.org/debian/ stretch - * [mirror2]: http://cdn-fastly.deb.debian.org/debian/ stretch [src] - * [mirror3]: http://cdn-fastly.deb.debian.org/debian/ stretch - * [mirror4]: http://download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/ ./ + * [mirror1]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch + * [mirror2]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch [src] + * [mirror3]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch + * [mirror4]: http://repo.aptly.info/system-tests/download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/ ./ To get more information about mirror, run `aptly mirror show `. diff --git a/system/t04_mirror/ListMirror6Test_gold b/system/t04_mirror/ListMirror6Test_gold index cf8b565e..18cfd1c3 100644 --- a/system/t04_mirror/ListMirror6Test_gold +++ b/system/t04_mirror/ListMirror6Test_gold @@ -1,7 +1,7 @@ [ { "Name": "mirror1", - "ArchiveRoot": "http://cdn-fastly.deb.debian.org/debian/", + "ArchiveRoot": "http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/", "Distribution": "stretch", "Components": [ "main", @@ -46,7 +46,7 @@ }, { "Name": "mirror2", - "ArchiveRoot": "http://cdn-fastly.deb.debian.org/debian/", + "ArchiveRoot": "http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/", "Distribution": "stretch", "Components": [ "contrib" @@ -89,7 +89,7 @@ }, { "Name": "mirror3", - "ArchiveRoot": "http://cdn-fastly.deb.debian.org/debian/", + "ArchiveRoot": "http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/", "Distribution": "stretch", "Components": [ "non-free" @@ -123,10 +123,13 @@ }, { "Name": "mirror4", - "ArchiveRoot": "http://download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/", + "ArchiveRoot": "http://repo.aptly.info/system-tests/download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/", "Distribution": "./", "Components": null, - "Architectures": null, + "Architectures": [ + "amd64", + "i386" + ], "Meta": { "Architectures": "i386 amd64", "Archive": "Debian_9.0", diff --git a/system/t04_mirror/ShowMirror1Test_gold b/system/t04_mirror/ShowMirror1Test_gold index 44e12aec..3acf9b5b 100644 --- a/system/t04_mirror/ShowMirror1Test_gold +++ b/system/t04_mirror/ShowMirror1Test_gold @@ -1,5 +1,5 @@ Name: mirror1 -Archive Root URL: http://cdn-fastly.deb.debian.org/debian/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ Distribution: stretch Components: main, contrib, non-free Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x diff --git a/system/t04_mirror/ShowMirror4Test_gold b/system/t04_mirror/ShowMirror4Test_gold index c531b649..48ff3286 100644 --- a/system/t04_mirror/ShowMirror4Test_gold +++ b/system/t04_mirror/ShowMirror4Test_gold @@ -1,5 +1,5 @@ Name: mirror4 -Archive Root URL: http://security.debian.org/ +Archive Root URL: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ Distribution: stretch/updates Components: main Architectures: amd64, arm64, armel, armhf, i386 diff --git a/system/t04_mirror/ShowMirror5Test_gold b/system/t04_mirror/ShowMirror5Test_gold index 5e5aaa63..d8ba1780 100644 --- a/system/t04_mirror/ShowMirror5Test_gold +++ b/system/t04_mirror/ShowMirror5Test_gold @@ -1,7 +1,6 @@ { - "UUID": "82ca6517-ab0b-4be9-81bd-e884a07167f2", "Name": "mirror1", - "ArchiveRoot": "http://cdn-fastly.deb.debian.org/debian/", + "ArchiveRoot": "http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/", "Distribution": "stretch", "Components": [ "main", diff --git a/system/t04_mirror/ShowMirror8Test_gold b/system/t04_mirror/ShowMirror8Test_gold index 2b8ae8fe..9d3f70c5 100644 --- a/system/t04_mirror/ShowMirror8Test_gold +++ b/system/t04_mirror/ShowMirror8Test_gold @@ -1,7 +1,6 @@ { - "UUID": "548dbdb6-75d6-42ac-80de-d6aff8012f83", "Name": "mirror4", - "ArchiveRoot": "http://security.debian.org/", + "ArchiveRoot": "http://repo.aptly.info/system-tests/archive.debian.org/debian-security/", "Distribution": "stretch/updates", "Components": [ "main" @@ -18,12 +17,11 @@ "Architectures": "amd64 arm64 armel armhf i386", "Codename": "stretch", "Components": "updates/main updates/contrib updates/non-free", - "Date": "Wed, 26 Jan 2022 10:02:07 UTC", + "Date": "anytime", "Description": " Debian 9 Security Updates\n", "Label": "Debian-Security", "Origin": "Debian", "Suite": "oldoldstable", - "Valid-Until": "Sat, 05 Feb 2022 10:02:07 UTC", "Version": "9" }, "LastDownloadDate": "0001-01-01T00:00:00Z", diff --git a/system/t04_mirror/UpdateMirror10Test_gold b/system/t04_mirror/UpdateMirror10Test_gold index e1e94596..3b4ab6f2 100644 --- a/system/t04_mirror/UpdateMirror10Test_gold +++ b/system/t04_mirror/UpdateMirror10Test_gold @@ -2,34 +2,30 @@ Applying filter... Building download queue... -Download queue: 9 items (3.50 MiB) +Download queue: 17 items (47.22 MiB) Downloading & parsing package files... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Sources.bz2... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/pkg-r-autopkgtest_20180403~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward-data_0.6.5-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_i386.deb... -Mirror `flat-src` has been successfully updated. -Packages filtered: 107 -> 9. -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2 -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Sources.bz2 -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/pkg-r-autopkgtest_20180403~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward-data_0.6.5-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_i386.deb -gpgv: aka "Johannes Ranke " -gpgv: Good signature from "Johannes Ranke (Wissenschaftlicher Berater) " -gpgv: RSA key ID 115C3D8A \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/InRelease +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Sources.bz2 +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-2~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2-dbgsym_3.5.12-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2-dbgsym_3.5.12-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2_3.5.12-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2_3.5.12-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-data_0.7.5-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12.orig.tar.gz +Mirror `flat-src` has been updated successfully. +Packages filtered: 110 -> 13. +gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 +gpgv: Good signature from "Johannes Ranke " +gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror11FTPTest_gold b/system/t04_mirror/UpdateMirror11FTPTest_gold new file mode 100644 index 00000000..dc4bab13 --- /dev/null +++ b/system/t04_mirror/UpdateMirror11FTPTest_gold @@ -0,0 +1,31 @@ + + +Applying filter... +Building download queue... +Download Error: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease +Download queue: 3 items (354.29 KiB) +Downloading & parsing package files... +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sed/sed_4.4-1_i386.deb +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sensible-utils/sensible-utils_0.0.9+deb9u1_all.deb +Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sysvinit/sysvinit-utils_2.88dsf-59.9_i386.deb +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease +Mirror `stretch-main` has been updated successfully. +Packages filtered: 50604 -> 3. +Retrying 0 http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease... +gpgv: issuer "debian-release@lists.debian.org" +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 +gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " +gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " +gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " +gpgv: Good signature from "Debian Stable Release Key (9/stretch) " +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror11Test_gold b/system/t04_mirror/UpdateMirror11Test_gold deleted file mode 100644 index 735d7085..00000000 --- a/system/t04_mirror/UpdateMirror11Test_gold +++ /dev/null @@ -1,32 +0,0 @@ - - -Applying filter... -Building download queue... -Download queue: 3 items (354.29 KiB) -Downloading & parsing package files... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/Release... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/Release.gpg... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/main/binary-i386/Packages.gz... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sed/sed_4.4-1_i386.deb... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sensible-utils/sensible-utils_0.0.9+deb9u1_all.deb... -Downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sysvinit/sysvinit-utils_2.88dsf-59.9_i386.deb... -Error downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease: HTTP code 404 while fetching https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease retrying... -Giving up on https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease... -Mirror `stretch-main` has been successfully updated. -Packages filtered: 50604 -> 3. -Retrying 0 https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease... -Success downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/Release -Success downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/Release.gpg -Success downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/main/binary-i386/Packages.gz -Success downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sed/sed_4.4-1_i386.deb -Success downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sensible-utils/sensible-utils_0.0.9+deb9u1_all.deb -Success downloading https://snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sysvinit/sysvinit-utils_2.88dsf-59.9_i386.deb -gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " -gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " -gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " -gpgv: Good signature from "Debian Stable Release Key (9/stretch) " -gpgv: RSA key ID B7D453EC -gpgv: RSA key ID 22F3D138 -gpgv: RSA key ID 1A7B6500 -gpgv: RSA key ID 386FA1D9 \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror12Test_gold b/system/t04_mirror/UpdateMirror12Test_gold index 34071495..f9ad32d5 100644 --- a/system/t04_mirror/UpdateMirror12Test_gold +++ b/system/t04_mirror/UpdateMirror12Test_gold @@ -2,79 +2,54 @@ Applying filter... Building download queue... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease Download queue: 20 items (11.93 MiB) Downloading & parsing package files... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-amd64/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-i386/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/debian-installer/binary-amd64/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/debian-installer/binary-i386/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/binary-amd64/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/binary-i386/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/debian-installer/binary-amd64/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/debian-installer/binary-i386/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/dirmngr_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/dirmngr_2.1.18-8~deb9u4_i386.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg-agent_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg-agent_2.1.18-8~deb9u4_i386.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg-l10n_2.1.18-8~deb9u4_all.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg2_2.1.18-8~deb9u4_all.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg_2.1.18-8~deb9u4_i386.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgsm_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgsm_2.1.18-8~deb9u4_i386.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-static_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-static_2.1.18-8~deb9u4_i386.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-udeb_2.1.18-8~deb9u4_amd64.udeb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-udeb_2.1.18-8~deb9u4_i386.udeb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-win32_2.1.18-8~deb9u4_all.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv2_2.1.18-8~deb9u4_all.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv_2.1.18-8~deb9u4_i386.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_amd64.deb... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_i386.deb... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Mirror `stretch` has been successfully updated. +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-amd64/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/debian-installer/binary-amd64/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/debian-installer/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/binary-amd64/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/debian-installer/binary-amd64/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/debian-installer/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/dirmngr_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/dirmngr_2.1.18-8~deb9u4_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gnupg-agent_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gnupg-agent_2.1.18-8~deb9u4_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gnupg-l10n_2.1.18-8~deb9u4_all.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gnupg2_2.1.18-8~deb9u4_all.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gnupg_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gnupg_2.1.18-8~deb9u4_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgsm_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgsm_2.1.18-8~deb9u4_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv-static_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv-static_2.1.18-8~deb9u4_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv-udeb_2.1.18-8~deb9u4_amd64.udeb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv-udeb_2.1.18-8~deb9u4_i386.udeb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv-win32_2.1.18-8~deb9u4_all.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv2_2.1.18-8~deb9u4_all.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/gpgv_2.1.18-8~deb9u4_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_i386.deb +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Mirror `stretch` has been updated successfully. Packages filtered: 78248 -> 20. -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-amd64/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-i386/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/debian-installer/binary-amd64/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/debian-installer/binary-i386/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/binary-amd64/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/binary-i386/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/debian-installer/binary-amd64/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/debian-installer/binary-i386/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/dirmngr_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/dirmngr_2.1.18-8~deb9u4_i386.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg-agent_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg-agent_2.1.18-8~deb9u4_i386.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg-l10n_2.1.18-8~deb9u4_all.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg2_2.1.18-8~deb9u4_all.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gnupg_2.1.18-8~deb9u4_i386.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgsm_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgsm_2.1.18-8~deb9u4_i386.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-static_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-static_2.1.18-8~deb9u4_i386.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-udeb_2.1.18-8~deb9u4_amd64.udeb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-udeb_2.1.18-8~deb9u4_i386.udeb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv-win32_2.1.18-8~deb9u4_all.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv2_2.1.18-8~deb9u4_all.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/gpgv_2.1.18-8~deb9u4_i386.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_amd64.deb -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_i386.deb +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +gpgv: issuer "debian-release@lists.debian.org" +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " gpgv: Good signature from "Debian Stable Release Key (9/stretch) " -gpgv: RSA key ID B7D453EC -gpgv: RSA key ID 22F3D138 -gpgv: RSA key ID 1A7B6500 -gpgv: RSA key ID 386FA1D9 \ No newline at end of file +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror13Test_gold b/system/t04_mirror/UpdateMirror13Test_gold index 4f6c6a66..b00c4ce3 100644 --- a/system/t04_mirror/UpdateMirror13Test_gold +++ b/system/t04_mirror/UpdateMirror13Test_gold @@ -3,114 +3,59 @@ Building download queue... Download queue: 52 items (19.79 MiB) Downloading & parsing package files... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_i386.deb... -Downloading https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-doc_3.0.4-1~wheezy_all.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-doc_3.0.5-1~wheezy_all.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_i386.deb... -Downloading https://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 https://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... -Downloading https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_amd64.deb... -Downloading https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_amd64.deb... -Downloading https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1+nmu1%29/varnish-agent_2.2.1+nmu1~wheezy_amd64.deb... -Downloading https://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 https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb... -Mirror `varnish` has been successfully updated. -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi1_3.0.6-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi1_3.0.6-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-doc_3.0.4-1~wheezy_all.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-doc_3.0.5-1~wheezy_all.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish_3.0.6-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish_3.0.6-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%281.16.0%29/varnish-agent_1.16.0~wheezy_all.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1+nmu1%29/varnish-agent_2.2.1+nmu1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.0%29/varnish-agent_3.0.0~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.1%29/varnish-agent_3.0.1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 +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/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-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/libvarnishapi-dev_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/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.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/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/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_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/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-doc_3.0.4-1~wheezy_all.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-doc_3.0.5-1~wheezy_all.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_i386.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/v/varnish%20%283.0.6-1%29/varnish-dbg_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.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/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/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%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/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.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_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%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%282.2.0%29/varnish-agent_2.2.0~wheezy_amd64.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/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.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-agent%20%282.2.1+nmu1%29/varnish-agent_2.2.1+nmu1~wheezy_amd64.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-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/varnish-dbg_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb +Mirror `varnish` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror14Test_gold b/system/t04_mirror/UpdateMirror14Test_gold index 2c66db83..edcc3695 100644 --- a/system/t04_mirror/UpdateMirror14Test_gold +++ b/system/t04_mirror/UpdateMirror14Test_gold @@ -3,10 +3,7 @@ Building download queue... Download queue: 0 items (0 B) Downloading & parsing package files... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2... -Mirror `varnish` has been successfully updated. -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2 \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2 +Mirror `varnish` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror15Test_gold b/system/t04_mirror/UpdateMirror15Test_gold index 3c11f1ff..d218085c 100644 --- a/system/t04_mirror/UpdateMirror15Test_gold +++ b/system/t04_mirror/UpdateMirror15Test_gold @@ -6,4 +6,4 @@ Downloading & parsing package files... Downloading https://dl.bintray.com/smira/deb/Packages.bz2... Downloading https://dl.bintray.com/smira/deb/Release... Downloading https://dl.bintray.com/smira/deb/libboost-program-options-dev_1.49.0.1_i386.deb... -Mirror `bintray` has been successfully updated. \ No newline at end of file +Mirror `bintray` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror16Test_gold b/system/t04_mirror/UpdateMirror16Test_gold index 3c11f1ff..d218085c 100644 --- a/system/t04_mirror/UpdateMirror16Test_gold +++ b/system/t04_mirror/UpdateMirror16Test_gold @@ -6,4 +6,4 @@ Downloading & parsing package files... Downloading https://dl.bintray.com/smira/deb/Packages.bz2... Downloading https://dl.bintray.com/smira/deb/Release... Downloading https://dl.bintray.com/smira/deb/libboost-program-options-dev_1.49.0.1_i386.deb... -Mirror `bintray` has been successfully updated. \ No newline at end of file +Mirror `bintray` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror17Test_gold b/system/t04_mirror/UpdateMirror17Test_gold index b99be85c..cddc0d3e 100644 --- a/system/t04_mirror/UpdateMirror17Test_gold +++ b/system/t04_mirror/UpdateMirror17Test_gold @@ -4,9 +4,7 @@ Applying filter... Building download queue... Download queue: 0 items (0 B) Downloading & parsing package files... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-i386/Packages.gz... -Mirror `stretch` has been successfully updated. -Packages filtered: 50604 -> 1. -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-i386/Packages.gz \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Mirror `stretch` has been updated successfully. +Packages filtered: 50604 -> 1. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror18Test_gold b/system/t04_mirror/UpdateMirror18Test_gold index 33e2dd98..30dfc184 100644 --- a/system/t04_mirror/UpdateMirror18Test_gold +++ b/system/t04_mirror/UpdateMirror18Test_gold @@ -4,11 +4,8 @@ Applying filter... Building download queue... Download queue: 1 items (3.35 KiB) Downloading & parsing package files... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-i386/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb... -Mirror `stretch` has been successfully updated. -Packages filtered: 50604 -> 1. -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-i386/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb +Mirror `stretch` has been updated successfully. +Packages filtered: 50604 -> 1. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror19Test_gold b/system/t04_mirror/UpdateMirror19Test_gold index b4c1abc7..ef4a8634 100644 --- a/system/t04_mirror/UpdateMirror19Test_gold +++ b/system/t04_mirror/UpdateMirror19Test_gold @@ -1,17 +1,15 @@ -Downloading https://packages.pagerduty.com/pdagent/deb/InRelease... -Error downloading https://packages.pagerduty.com/pdagent/deb/InRelease: HTTP code 404 while fetching https://packages.pagerduty.com/pdagent/deb/InRelease retrying... -Retrying 0 https://packages.pagerduty.com/pdagent/deb/InRelease... -Giving up on https://packages.pagerduty.com/pdagent/deb/InRelease... -Downloading https://packages.pagerduty.com/pdagent/deb/Release... -Success downloading https://packages.pagerduty.com/pdagent/deb/Release -Downloading https://packages.pagerduty.com/pdagent/deb/Release.gpg... -Success downloading https://packages.pagerduty.com/pdagent/deb/Release.gpg -gpgv: RSA key ID F8253540 +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease +Retrying 0 http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease... +Download Error: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/Release +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/Release.gpg +gpgv: Signature made Tue Jun 30 15:56:03 2020 UTC +gpgv: using RSA key AE0396CFF8253540 gpgv: Good signature from "Package Maintainer (PagerDuty, Inc.) " Downloading & parsing package files... -Downloading https://packages.pagerduty.com/pdagent/deb/Packages.gz... -Success downloading https://packages.pagerduty.com/pdagent/deb/Packages.gz +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/Packages.gz Building download queue... Download queue: 23 items (3.46 MiB) -Mirror `pagerduty` has been successfully updated. +Mirror `pagerduty` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror1Test_gold b/system/t04_mirror/UpdateMirror1Test_gold index 4f6c6a66..b00c4ce3 100644 --- a/system/t04_mirror/UpdateMirror1Test_gold +++ b/system/t04_mirror/UpdateMirror1Test_gold @@ -3,114 +3,59 @@ Building download queue... Download queue: 52 items (19.79 MiB) Downloading & parsing package files... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_i386.deb... -Downloading https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-doc_3.0.4-1~wheezy_all.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-doc_3.0.5-1~wheezy_all.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_i386.deb... -Downloading https://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 https://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... -Downloading https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_amd64.deb... -Downloading https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_amd64.deb... -Downloading https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1+nmu1%29/varnish-agent_2.2.1+nmu1~wheezy_amd64.deb... -Downloading https://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 https://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 https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb... -Downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb... -Mirror `varnish` has been successfully updated. -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi1_3.0.6-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi1_3.0.6-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-doc_3.0.4-1~wheezy_all.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-doc_3.0.5-1~wheezy_all.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish_3.0.6-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish_3.0.6-1~wheezy_i386.deb -Success downloading https://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 -Success downloading https://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 -Success downloading https://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 -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%281.16.0%29/varnish-agent_1.16.0~wheezy_all.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1+nmu1%29/varnish-agent_2.2.1+nmu1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.0%29/varnish-agent_3.0.0~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.1%29/varnish-agent_3.0.1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb -Success downloading https://packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 +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/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.4-1%29/libvarnishapi1_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.5-1%29/libvarnishapi1_3.0.5-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/libvarnishapi-dev_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/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.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/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/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_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/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-dbg_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish-doc_3.0.4-1~wheezy_all.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.4-1%29/varnish_3.0.4-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-dbg_3.0.5-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish-doc_3.0.5-1~wheezy_all.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.5-1%29/varnish_3.0.5-1~wheezy_i386.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/v/varnish%20%283.0.6-1%29/varnish-dbg_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.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/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/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%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/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.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_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%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%282.2.0%29/varnish-agent_2.2.0~wheezy_amd64.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/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.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-agent%20%282.2.1+nmu1%29/varnish-agent_2.2.1+nmu1~wheezy_amd64.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-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/varnish-dbg_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb +Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb +Mirror `varnish` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror20Test_gold b/system/t04_mirror/UpdateMirror20Test_gold index ef7ecd29..726afd93 100644 --- a/system/t04_mirror/UpdateMirror20Test_gold +++ b/system/t04_mirror/UpdateMirror20Test_gold @@ -1,23 +1,14 @@ - aka "Johannes Ranke " Applying filter... Building download queue... -Download queue: 4 items (338.12 KiB) +Download queue: 2 items (173.67 KiB) Downloading & parsing package files... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_i386.deb... -Mirror `flat` has been successfully updated. -Packages filtered: 78 -> 4. -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2 -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_i386.deb -openpgp: Good signature from "Johannes Ranke (Wissenschaftlicher Berater) " -openpgp: RSA key ID FCAE2A0E115C3D8A \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/InRelease +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_i386.deb +Mirror `flat` has been updated successfully. +Packages filtered: 89 -> 2. +openpgp: Good signature from "Johannes Ranke " +openpgp: RSA key ID B8F25A8A73EACF41 \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror21Test_gold b/system/t04_mirror/UpdateMirror21Test_gold index f74816a9..e864bf91 100644 --- a/system/t04_mirror/UpdateMirror21Test_gold +++ b/system/t04_mirror/UpdateMirror21Test_gold @@ -1,17 +1,14 @@ -Downloading https://packages.pagerduty.com/pdagent/deb/InRelease... -Error downloading https://packages.pagerduty.com/pdagent/deb/InRelease: HTTP code 404 while fetching https://packages.pagerduty.com/pdagent/deb/InRelease retrying... -Retrying 0 https://packages.pagerduty.com/pdagent/deb/InRelease... -Giving up on https://packages.pagerduty.com/pdagent/deb/InRelease... -Downloading https://packages.pagerduty.com/pdagent/deb/Release... -Success downloading https://packages.pagerduty.com/pdagent/deb/Release -Downloading https://packages.pagerduty.com/pdagent/deb/Release.gpg... -Success downloading https://packages.pagerduty.com/pdagent/deb/Release.gpg +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease +Retrying 0 http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease... +Download Error: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/InRelease +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/Release +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/Release.gpg openpgp: RSA key ID AE0396CFF8253540 openpgp: Good signature from "Package Maintainer (PagerDuty, Inc.) " Downloading & parsing package files... -Downloading https://packages.pagerduty.com/pdagent/deb/Packages.gz... -Success downloading https://packages.pagerduty.com/pdagent/deb/Packages.gz +Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/deb/Packages.gz Building download queue... Download queue: 23 items (3.46 MiB) -Mirror `pagerduty` has been successfully updated. +Mirror `pagerduty` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror22Test_gold b/system/t04_mirror/UpdateMirror22Test_gold index 34edb82c..9c76ac64 100644 --- a/system/t04_mirror/UpdateMirror22Test_gold +++ b/system/t04_mirror/UpdateMirror22Test_gold @@ -1,13 +1,11 @@ -Downloading https://nvidia.github.io/libnvidia-container/ubuntu16.04/amd64/InRelease... -Success downloading https://nvidia.github.io/libnvidia-container/ubuntu16.04/amd64/InRelease +Downloading: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/InRelease openpgp: RSA key ID DDCAE044F796ECB0 openpgp: Good signature from "NVIDIA CORPORATION (Open Source Projects) " Downloading & parsing package files... -Downloading https://nvidia.github.io/libnvidia-container/ubuntu16.04/amd64/Packages.xz... -Success downloading https://nvidia.github.io/libnvidia-container/ubuntu16.04/amd64/Packages.xz +Downloading: http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64/Packages.xz Applying filter... Building download queue... Download queue: 0 items (0 B) -Mirror `libnvidia-container` has been successfully updated. +Mirror `libnvidia-container` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror23Test_gold b/system/t04_mirror/UpdateMirror23Test_gold index 2f7f9fd7..5b172366 100644 --- a/system/t04_mirror/UpdateMirror23Test_gold +++ b/system/t04_mirror/UpdateMirror23Test_gold @@ -2,49 +2,41 @@ Applying filter... Building download queue... +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS Download queue: 8 items (20.93 MiB) Downloading & parsing package files... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-s390x/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/MANIFEST... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/MANIFEST.udebs... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/MD5SUMS... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/SHA256SUMS... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/debian.exec... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/initrd.debian... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/kernel.debian... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/parmfile.debian... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/udeb.list... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/binary-s390x/Packages.gz... -Downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease retrying... -Error downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS: HTTP code 404 while fetching http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS retrying... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Giving up on http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS... -Mirror `stretch` has been successfully updated. +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-s390x/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/MANIFEST +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/MANIFEST.udebs +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/MD5SUMS +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/SHA256SUMS +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/generic/debian.exec +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/generic/initrd.debian +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/generic/kernel.debian +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/generic/parmfile.debian +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/installer-s390x/current/images/udeb.list +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/binary-s390x/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS +Mirror `stretch` has been updated successfully. Packages filtered: 49256 -> 1. -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/InRelease... -Retrying 0 http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS... -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/Release.gpg -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/binary-s390x/Packages.gz -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/MANIFEST -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/MANIFEST.udebs -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/MD5SUMS -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/SHA256SUMS -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/debian.exec -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/initrd.debian -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/kernel.debian -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/generic/parmfile.debian -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/main/installer-s390x/current/images/udeb.list -Success downloading http://cdn-fastly.deb.debian.org/debian/dists/stretch/non-free/binary-s390x/Packages.gz +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS... +gpgv: issuer "debian-release@lists.debian.org" +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " gpgv: Good signature from "Debian Stable Release Key (9/stretch) " -gpgv: RSA key ID B7D453EC -gpgv: RSA key ID 22F3D138 -gpgv: RSA key ID 1A7B6500 -gpgv: RSA key ID 386FA1D9 \ No newline at end of file +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror24Test_gold b/system/t04_mirror/UpdateMirror24Test_gold index 34c99295..dcc27631 100644 --- a/system/t04_mirror/UpdateMirror24Test_gold +++ b/system/t04_mirror/UpdateMirror24Test_gold @@ -2,109 +2,70 @@ Applying filter... Building download queue... +Download Error: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease +Download Error: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS Download queue: 37 items (166.14 MiB) Downloading & parsing package files... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/Release... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/Release.gpg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/binary-amd64/Packages.bz2... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/MANIFEST... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/MANIFEST.udebs... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/SHA256SUMS... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/SHA256SUMS.gpg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/debian-cd_info.tar.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/initrd.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/vmlinuz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/xen/xm-debian.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/boot.img.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/initrd.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/vmlinuz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/boot.img.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/mini.iso... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/netboot.tar.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/adtxt.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/exithelp.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f1.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f10.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f2.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f3.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f4.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f5.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f6.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f7.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f8.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f9.txt... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/menu.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/prompt.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/rqtxt.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/splash.png... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/stdmenu.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/syslinux.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/txt.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/vesamenu.c32... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/initrd.gz... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/linux... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/pxelinux.0... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/xen/xm-debian.cfg... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/udeb.list... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/binary-amd64/Packages.bz2... -Downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS... -Error downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease: HTTP code 404 while fetching http://us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease retrying... -Error downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS: HTTP code 404 while fetching http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS retrying... -Giving up on http://us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease... -Giving up on http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS... -Mirror `trusty` has been successfully updated. +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/Release +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/Release.gpg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/binary-amd64/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/MANIFEST +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/MANIFEST.udebs +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/SHA256SUMS +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/SHA256SUMS.gpg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/debian-cd_info.tar.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/initrd.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/vmlinuz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/xen/xm-debian.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/boot.img.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/initrd.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/vmlinuz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/boot.img.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/mini.iso +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/netboot.tar.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/adtxt.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/exithelp.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f1.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f10.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f2.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f3.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f4.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f5.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f6.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f7.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f8.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f9.txt +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/menu.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/prompt.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/rqtxt.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/splash.png +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/stdmenu.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/syslinux.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/txt.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/vesamenu.c32 +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/initrd.gz +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/linux +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/pxelinux.0 +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/xen/xm-debian.cfg +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/udeb.list +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/binary-amd64/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS +Mirror `trusty` has been updated successfully. Packages filtered: 8616 -> 1. -Retrying 0 http://us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease... -Retrying 0 http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS... -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/Release -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/Release.gpg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/binary-amd64/Packages.bz2 -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/MANIFEST -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/MANIFEST.udebs -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/SHA256SUMS -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/SHA256SUMS.gpg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/debian-cd_info.tar.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/initrd.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/vmlinuz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/cdrom/xen/xm-debian.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/boot.img.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/initrd.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/hd-media/vmlinuz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/boot.img.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/mini.iso -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/netboot.tar.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/adtxt.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/exithelp.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f1.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f10.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f2.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f3.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f4.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f5.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f6.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f7.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f8.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/f9.txt -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/menu.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/prompt.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/rqtxt.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/splash.png -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/stdmenu.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/syslinux.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/txt.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/boot-screens/vesamenu.c32 -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/initrd.gz -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/linux -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/pxelinux.0 -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/netboot/xen/xm-debian.cfg -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/main/installer-amd64/current/images/udeb.list -Success downloading http://us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/binary-amd64/Packages.bz2 +Retrying 0 http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease... +Retrying 0 http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS... +gpgv: using DSA key 40976EAF437D05B5 +gpgv: using DSA key 40976EAF437D05B5 +gpgv: using RSA key 3B4FE6ACC0B21F32 +gpgv: using RSA key 3B4FE6ACC0B21F32 gpgv: Good signature from "Ubuntu Archive Automatic Signing Key (2012) " gpgv: Good signature from "Ubuntu Archive Automatic Signing Key (2012) " gpgv: Good signature from "Ubuntu Archive Automatic Signing Key " gpgv: Good signature from "Ubuntu Archive Automatic Signing Key " -gpgv: DSA key ID 437D05B5 -gpgv: RSA key ID C0B21F32 -gpgv: DSA key ID 437D05B5 -gpgv: RSA key ID C0B21F32 \ No newline at end of file +gpgv: Signature made Thu May 8 14:20:33 2014 UTC +gpgv: Signature made Thu May 8 14:20:33 2014 UTC +gpgv: Signature made Tue Apr 15 22:05:21 2014 UTC +gpgv: Signature made Tue Apr 15 22:05:21 2014 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror25Test_gold b/system/t04_mirror/UpdateMirror25Test_gold new file mode 100644 index 00000000..41b197ca --- /dev/null +++ b/system/t04_mirror/UpdateMirror25Test_gold @@ -0,0 +1,22 @@ + +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.gz +Download Error: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.xz +Downloading & parsing package files... +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.xz +ERROR: unable to update: HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.gz +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.xz +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages... +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.gz... +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/main/binary-i386/Packages.xz... +gpgv: using RSA key 379483D8B60160B155B372DDAA8E81B4331F7F50 +gpgv: using RSA key 5237CEEEF212F3D51C74ABE0112695A0E562B32A +gpgv: Good signature from "Debian Security Archive Automatic Signing Key (10/buster) " +gpgv: Good signature from "Debian Security Archive Automatic Signing Key (9/stretch) " +gpgv: Signature made Sat Feb 18 04:22:45 2023 UTC +gpgv: Signature made Sat Feb 18 04:22:45 2023 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror26Test_gold b/system/t04_mirror/UpdateMirror26Test_gold new file mode 100644 index 00000000..b460083f --- /dev/null +++ b/system/t04_mirror/UpdateMirror26Test_gold @@ -0,0 +1,33 @@ + + +Applying filter... +Building download queue... +Download queue: 1 items (3.35 KiB) +Downloading & parsing package files... +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb +Mirror `grab` has been updated successfully. +Packages filtered: 50604 -> 1. +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +gpgv: issuer "debian-release@lists.debian.org" +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 +gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " +gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " +gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " +gpgv: Good signature from "Debian Stable Release Key (9/stretch) " +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror27Test_gold b/system/t04_mirror/UpdateMirror27Test_gold new file mode 100644 index 00000000..677e80cb --- /dev/null +++ b/system/t04_mirror/UpdateMirror27Test_gold @@ -0,0 +1,39 @@ + + server returned 404 Not Found +Applying filter... +Building download queue... +Download queue: 2 items (6.70 KiB) +Downloading & parsing package files... +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release.gpg +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-amd64/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-amd64/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_amd64.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb +Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb +ERROR: unable to update: download errors: +Packages filtered: 76844 -> 2. +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_amd64.deb +Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_amd64.deb +gpgv: issuer "debian-release@lists.debian.org" +gpgv: using RSA key 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 +gpgv: using RSA key 067E3C456BAE240ACEE88F6FEF0F382A1A7B6500 +gpgv: using RSA key 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC +gpgv: using RSA key A7236886F3CCCAAD148A27F80E98404D386FA1D9 +gpgv: Good signature from "Debian Archive Automatic Signing Key (10/buster) " +gpgv: Good signature from "Debian Archive Automatic Signing Key (11/bullseye) " +gpgv: Good signature from "Debian Archive Automatic Signing Key (9/stretch) " +gpgv: Good signature from "Debian Stable Release Key (9/stretch) " +gpgv: Signature made Sat Aug 14 07:43:24 2021 UTC +gpgv: Signature made Sat Aug 14 07:43:25 2021 UTC +gpgv: Signature made Sat Aug 14 08:26:43 2021 UTC +gpgv: Signature made Sat Aug 14 08:46:19 2021 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror3Test_gold b/system/t04_mirror/UpdateMirror3Test_gold index cc9a36e1..cdb53260 100644 --- a/system/t04_mirror/UpdateMirror3Test_gold +++ b/system/t04_mirror/UpdateMirror3Test_gold @@ -1,8 +1,7 @@ -Downloading ${url}dists/hardy/Release... -Success downloading ${url}dists/hardy/Release +Downloading: ${url}dists/hardy/Release Downloading & parsing package files... -Downloading ${url}dists/hardy/main/binary-amd64/Packages... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages: ${url}dists/hardy/main/binary-amd64/Packages: sha256 hash mismatch "494414ded24da13c451b13b424928821351c78fce49f93d9e1b55f102790c206" != "8a21688ae769f2b4ffcaa366409f679d" retrying... +Downloading: ${url}dists/hardy/main/binary-amd64/Packages +Error (retrying): ${url}dists/hardy/main/binary-amd64/Packages: sha256 hash mismatch "494414ded24da13c451b13b424928821351c78fce49f93d9e1b55f102790c206" != "8a21688ae769f2b4ffcaa366409f679d" Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages... +Download Error: ${url}dists/hardy/main/binary-amd64/Packages ERROR: unable to update: ${url}dists/hardy/main/binary-amd64/Packages: sha256 hash mismatch "494414ded24da13c451b13b424928821351c78fce49f93d9e1b55f102790c206" != "8a21688ae769f2b4ffcaa366409f679d" diff --git a/system/t04_mirror/UpdateMirror4Test_gold b/system/t04_mirror/UpdateMirror4Test_gold index d8ba9728..b661f866 100644 --- a/system/t04_mirror/UpdateMirror4Test_gold +++ b/system/t04_mirror/UpdateMirror4Test_gold @@ -1,19 +1,17 @@ -Downloading ${url}dists/hardy/Release... -Success downloading ${url}dists/hardy/Release +Downloading: ${url}dists/hardy/Release Downloading & parsing package files... -Downloading ${url}dists/hardy/main/binary-amd64/Packages.bz2... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages.bz2: HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.bz2 retrying... +Downloading: ${url}dists/hardy/main/binary-amd64/Packages.bz2 +Error (retrying): HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.bz2 Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages.bz2... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages.bz2... -Downloading ${url}dists/hardy/main/binary-amd64/Packages.gz... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages.gz: HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.gz retrying... +Download Error: ${url}dists/hardy/main/binary-amd64/Packages.bz2 +Downloading: ${url}dists/hardy/main/binary-amd64/Packages.gz +Error (retrying): HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.gz Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages.gz... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages.gz... -Downloading ${url}dists/hardy/main/binary-amd64/Packages.xz... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages.xz: HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.xz retrying... +Download Error: ${url}dists/hardy/main/binary-amd64/Packages.gz +Downloading: ${url}dists/hardy/main/binary-amd64/Packages.xz +Error (retrying): HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.xz Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages.xz... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages.xz... -Downloading ${url}dists/hardy/main/binary-amd64/Packages... +Download Error: ${url}dists/hardy/main/binary-amd64/Packages.xz +Downloading: ${url}dists/hardy/main/binary-amd64/Packages WARNING: ${url}dists/hardy/main/binary-amd64/Packages: sha256 hash mismatch "494414ded24da13c451b13b424928821351c78fce49f93d9e1b55f102790c206" != "8a21688ae769f2b4ffcaa366409f679d" -Success downloading ${url}dists/hardy/main/binary-amd64/Packages ERROR: unable to update: malformed stanza syntax diff --git a/system/t04_mirror/UpdateMirror5Test_gold b/system/t04_mirror/UpdateMirror5Test_gold index 61d8087c..315f6201 100644 --- a/system/t04_mirror/UpdateMirror5Test_gold +++ b/system/t04_mirror/UpdateMirror5Test_gold @@ -1,13 +1,11 @@ -Downloading ${url}dists/hardy/Release... -Success downloading ${url}dists/hardy/Release +Downloading: ${url}dists/hardy/Release Downloading & parsing package files... -Downloading ${url}dists/hardy/main/binary-amd64/Packages... -Success downloading ${url}dists/hardy/main/binary-amd64/Packages +Downloading: ${url}dists/hardy/main/binary-amd64/Packages Building download queue... Download queue: 1 items (30 B) -Downloading ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb... -Error downloading ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb: sha1 hash mismatch "8d3a014000038725d6daf8771b42a0784253688f" != "66b27417d37e024c46526c2f6d358a754fc552f3" retrying... +Downloading: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb +Error (retrying): ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb: sha1 hash mismatch "8d3a014000038725d6daf8771b42a0784253688f" != "66b27417d37e024c46526c2f6d358a754fc552f3" Retrying 0 ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb... -Giving up on ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb... +Download Error: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb ERROR: unable to update: download errors: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb: sha1 hash mismatch "8d3a014000038725d6daf8771b42a0784253688f" != "66b27417d37e024c46526c2f6d358a754fc552f3" diff --git a/system/t04_mirror/UpdateMirror6Test_gold b/system/t04_mirror/UpdateMirror6Test_gold index cd5b73c4..000a38aa 100644 --- a/system/t04_mirror/UpdateMirror6Test_gold +++ b/system/t04_mirror/UpdateMirror6Test_gold @@ -1,24 +1,21 @@ -Downloading ${url}dists/hardy/Release... -Success downloading ${url}dists/hardy/Release +Downloading: ${url}dists/hardy/Release Downloading & parsing package files... -Downloading ${url}dists/hardy/main/binary-amd64/Packages.bz2... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages.bz2: HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.bz2 retrying... +Downloading: ${url}dists/hardy/main/binary-amd64/Packages.bz2 +Error (retrying): HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.bz2 Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages.bz2... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages.bz2... -Downloading ${url}dists/hardy/main/binary-amd64/Packages.gz... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages.gz: HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.gz retrying... +Download Error: ${url}dists/hardy/main/binary-amd64/Packages.bz2 +Downloading: ${url}dists/hardy/main/binary-amd64/Packages.gz +Error (retrying): HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.gz Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages.gz... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages.gz... -Downloading ${url}dists/hardy/main/binary-amd64/Packages.xz... -Error downloading ${url}dists/hardy/main/binary-amd64/Packages.xz: HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.xz retrying... +Download Error: ${url}dists/hardy/main/binary-amd64/Packages.gz +Downloading: ${url}dists/hardy/main/binary-amd64/Packages.xz +Error (retrying): HTTP code 404 while fetching ${url}dists/hardy/main/binary-amd64/Packages.xz Retrying 0 ${url}dists/hardy/main/binary-amd64/Packages.xz... -Giving up on ${url}dists/hardy/main/binary-amd64/Packages.xz... -Downloading ${url}dists/hardy/main/binary-amd64/Packages... -Success downloading ${url}dists/hardy/main/binary-amd64/Packages +Download Error: ${url}dists/hardy/main/binary-amd64/Packages.xz +Downloading: ${url}dists/hardy/main/binary-amd64/Packages Building download queue... Download queue: 1 items (30 B) -Downloading ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb... +Downloading: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb WARNING: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb: sha1 hash mismatch "8d3a014000038725d6daf8771b42a0784253688f" != "66b27417d37e024c46526c2f6d358a754fc552f3" -Success downloading ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb -Mirror `failure` has been successfully updated. +Mirror `failure` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror7Test_gold b/system/t04_mirror/UpdateMirror7Test_gold index 1f2a4b27..bc1279d8 100644 --- a/system/t04_mirror/UpdateMirror7Test_gold +++ b/system/t04_mirror/UpdateMirror7Test_gold @@ -1,169 +1,100 @@ Building download queue... -Download queue: 78 items (200.63 MiB) +Download queue: 89 items (158.97 MiB) Downloading & parsing package files... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/pkg-r-autopkgtest_20180403~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-boot_1.3-20-2~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-15-2~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-16-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.6-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward-data_0.6.5-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_i386.deb... -Mirror `flat` has been successfully updated. -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2 -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/pkg-r-autopkgtest_20180403~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-boot_1.3-20-2~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-15-2~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-16-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.6-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward-data_0.6.5-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_i386.deb -gpgv: aka "Johannes Ranke " -gpgv: Good signature from "Johannes Ranke (Wissenschaftlicher Berater) " -gpgv: RSA key ID 115C3D8A \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/InRelease +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-2~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2-dbgsym_3.5.12-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2-dbgsym_3.5.12-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2_3.5.12-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2_3.5.12-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core-dbgsym_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core-dbgsym_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-dev_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-html_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-boot_1.3-28.1-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class-dbgsym_7.3-22-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class-dbgsym_7.3-22-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster-dbgsym_2.1.4-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster-dbgsym_2.1.4-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster_2.1.4-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster_2.1.4-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-coda_0.19-4-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-codetools_0.2-19-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign-dbgsym_0.8.85-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign-dbgsym_0.8.85-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign_0.8.85-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign_0.8.85-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth-dbgsym_2.23-22-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth-dbgsym_2.23-22-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth_2.23-22-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth_2.23-22-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice-dbgsym_0.22-5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice-dbgsym_0.22-5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice_0.22-5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice_0.22-5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass-dbgsym_7.3-60-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass-dbgsym_7.3-60-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix-dbgsym_1.6-1.1-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix-dbgsym_1.6-1.1-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix_1.6-1.1-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix_1.6-1.1-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv-dbgsym_1.9-0-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv-dbgsym_1.9-0-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv_1.9-0-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv_1.9-0-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme-dbgsym_3.1.163-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme-dbgsym_3.1.163-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme_3.1.163-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme_3.1.163-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet-dbgsym_7.3-19-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet-dbgsym_7.3-19-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart-dbgsym_4.1.21-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart-dbgsym_4.1.21-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart_4.1.21-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart_4.1.21-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial-dbgsym_7.3-17-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial-dbgsym_7.3-17-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival-dbgsym_3.5-7-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival-dbgsym_3.5-7-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival_3.5-7-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival_3.5-7-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-doc-html_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-doc-info_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-doc-pdf_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib-dbgsym_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib-dbgsym_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-recommended_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-data_0.7.5-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_i386.deb +Mirror `flat` has been updated successfully. +gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 +gpgv: Good signature from "Johannes Ranke " +gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror8Test_gold b/system/t04_mirror/UpdateMirror8Test_gold index a299fe9d..382b15e2 100644 --- a/system/t04_mirror/UpdateMirror8Test_gold +++ b/system/t04_mirror/UpdateMirror8Test_gold @@ -1,23 +1,18 @@ -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... -Error downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease: HTTP code 404 while fetching http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease retrying... -Retrying 0 http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... -Giving up on http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release.gpg... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release.gpg -gpgv: RSA key ID 3B1F56C0 +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease +Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease +Retrying 0 http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease... +Download Error: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/Release.gpg +gpgv: Signature made Mon Oct 22 13:19:50 2012 UTC +gpgv: using RSA key A5279A973B1F56C0 gpgv: Good signature from "Launchpad sim" Downloading & parsing package files... -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-amd64/Packages.bz2... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-amd64/Packages.bz2 -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-armel/Packages.bz2... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-armel/Packages.bz2 -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-i386/Packages.bz2... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-i386/Packages.bz2 -Downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-powerpc/Packages.bz2... -Success downloading http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-powerpc/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-amd64/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-armel/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-i386/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/main/binary-powerpc/Packages.bz2 Building download queue... Download queue: 0 items (0 B) -Mirror `gnuplot-maverick-src` has been successfully updated. +Mirror `gnuplot-maverick-src` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror9Test_gold b/system/t04_mirror/UpdateMirror9Test_gold index 84d8e129..2c2dba87 100644 --- a/system/t04_mirror/UpdateMirror9Test_gold +++ b/system/t04_mirror/UpdateMirror9Test_gold @@ -1,343 +1,163 @@ Building download queue... -Download queue: 164 items (278.49 MiB) +Download queue: 151 items (213.34 MiB) Downloading & parsing package files... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Sources.bz2... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/boot_1.3-20-2~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/boot_1.3-20-2~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/boot_1.3-20.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/cluster_2.0.7-1-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/cluster_2.0.7-1-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/cluster_2.0.7-1.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-15-2~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-15-2~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-15.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-16-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-16-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-16.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/foreign_0.8.71-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/foreign_0.8.71-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/foreign_0.8.71.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/kernsmooth_2.23-15-3~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/kernsmooth_2.23-15-3~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/kernsmooth_2.23-15.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-35-1~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-35-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-35.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-38-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-38-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-38.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-26-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-26-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-26.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-27-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-27-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-27.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/nlme_3.1.137-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/nlme_3.1.137-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/nlme_3.1.137.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/pkg-r-autopkgtest_20180403~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-boot_1.3-20-2~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-15-2~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-16-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.6-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.2-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.3-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward-data_0.6.5-1~jessiecran.0_all.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_amd64.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_i386.deb... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-15-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-15-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-15.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-16-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-16-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-16.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rodbc_1.3-15-1~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rodbc_1.3-15-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rodbc_1.3-15.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpart_4.1-13-1~jessiecran.0.diff.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpart_4.1-13-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpart_4.1-13.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpy2_2.9.5-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpy2_2.9.5-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpy2_2.9.5.orig.tar.gz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/survival_2.43-3-1~jessiecran.0.debian.tar.xz... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/survival_2.43-3-1~jessiecran.0.dsc... -Downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/survival_2.43-3.orig.tar.gz... -Mirror `flat-src` has been successfully updated. -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/InRelease -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Packages.bz2 -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/Sources.bz2 -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/boot_1.3-20-2~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/boot_1.3-20-2~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/boot_1.3-20.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/cluster_2.0.7-1-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/cluster_2.0.7-1-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/cluster_2.0.7-1.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-15-2~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-15-2~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-15.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-16-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-16-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/codetools_0.2-16.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/dh-r_20180403~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/foreign_0.8.71-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/foreign_0.8.71-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/foreign_0.8.71.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/kernsmooth_2.23-15-3~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/kernsmooth_2.23-15-3~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/kernsmooth_2.23-15.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-35-1~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-35-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-35.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-38-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-38-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/lattice_0.20-38.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.5.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/littler_0.3.6.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-26-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-26-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-26.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-27-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-27-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/mgcv_1.8-27.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/nlme_3.1.137-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/nlme_3.1.137-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/nlme_3.1.137.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/pkg-r-autopkgtest_20180403~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/python3-rpy2_2.9.5-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.2-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core-dbg_3.5.3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.2-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-core_3.5.3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-dev_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base-html_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.2.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-base_3.5.3.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-boot_1.3-20-2~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-14.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-class_7.3-15.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-cluster_2.0.7-1-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1-2~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-coda_0.19-1.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-15-2~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-codetools_0.2-16-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-foreign_0.8.71-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-kernsmooth_2.23-15-3~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-35-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-lattice_0.20-38-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.5-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-littler_0.3.6-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mass_7.3-51.1.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-15-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-matrix_1.2-16-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-26-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-mgcv_1.8-27-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nlme_3.1.137-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-nnet_7.3-12.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rodbc_1.3-15-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-rpart_4.1-13-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11-2~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-spatial_7.3-11.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-cran-survival_2.43-3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-html_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-info_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-doc-pdf_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.2-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-mathlib_3.5.3-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.2-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/r-recommended_3.5.3-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward-data_0.6.5-1~jessiecran.0_all.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_amd64.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5-1~jessiecran.0_i386.deb -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rkward_0.6.5.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-15-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-15-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-15.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-16-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-16-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rmatrix_1.2-16.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rodbc_1.3-15-1~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rodbc_1.3-15-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rodbc_1.3-15.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpart_4.1-13-1~jessiecran.0.diff.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpart_4.1-13-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpart_4.1-13.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpy2_2.9.5-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpy2_2.9.5-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/rpy2_2.9.5.orig.tar.gz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/survival_2.43-3-1~jessiecran.0.debian.tar.xz -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/survival_2.43-3-1~jessiecran.0.dsc -Success downloading https://cloud.r-project.org/bin/linux/debian/jessie-cran35/survival_2.43-3.orig.tar.gz -gpgv: aka "Johannes Ranke " -gpgv: Good signature from "Johannes Ranke (Wissenschaftlicher Berater) " -gpgv: RSA key ID 115C3D8A \ No newline at end of file +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/InRelease +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Packages.bz2 +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Sources.bz2 +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/boot_1.3-28.1-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/boot_1.3-28.1-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/boot_1.3-28.1.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/cluster_2.1.4-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/cluster_2.1.4-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/cluster_2.1.4.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/codetools_0.2-19-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/codetools_0.2-19-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/codetools_0.2-19.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/foreign_0.8.85-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/foreign_0.8.85-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/foreign_0.8.85.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/kernsmooth_2.23-22-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/kernsmooth_2.23-22-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/kernsmooth_2.23-22.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/lattice_0.22-5-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/lattice_0.22-5-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/lattice_0.22-5.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-2~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-2~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18-2~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/littler_0.3.18.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/mgcv_1.9-0-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/mgcv_1.9-0-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/mgcv_1.9-0.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/nlme_3.1.163-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/nlme_3.1.163-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/nlme_3.1.163.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2-dbgsym_3.5.12-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2-dbgsym_3.5.12-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2_3.5.12-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/python3-rpy2_3.5.12-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core-dbgsym_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core-dbgsym_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-core_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-dev_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base-html_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base_4.3.2-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base_4.3.2-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-base_4.3.2.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-boot_1.3-28.1-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class-dbgsym_7.3-22-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class-dbgsym_7.3-22-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster-dbgsym_2.1.4-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster-dbgsym_2.1.4-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster_2.1.4-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-cluster_2.1.4-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-coda_0.19-4-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-coda_0.19-4-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-coda_0.19-4-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-coda_0.19-4.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-codetools_0.2-19-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign-dbgsym_0.8.85-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign-dbgsym_0.8.85-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign_0.8.85-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-foreign_0.8.85-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth-dbgsym_2.23-22-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth-dbgsym_2.23-22-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth_2.23-22-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-kernsmooth_2.23-22-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice-dbgsym_0.22-5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice-dbgsym_0.22-5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice_0.22-5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-lattice_0.22-5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler-dbgsym_0.3.18-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-littler_0.3.18-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass-dbgsym_7.3-60-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass-dbgsym_7.3-60-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60-2~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60-2~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mass_7.3-60.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix-dbgsym_1.6-1.1-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix-dbgsym_1.6-1.1-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix_1.6-1.1-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-matrix_1.6-1.1-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv-dbgsym_1.9-0-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv-dbgsym_1.9-0-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv_1.9-0-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-mgcv_1.9-0-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme-dbgsym_3.1.163-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme-dbgsym_3.1.163-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme_3.1.163-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nlme_3.1.163-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet-dbgsym_7.3-19-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet-dbgsym_7.3-19-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19-2~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19-2~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19-2~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19-2~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-nnet_7.3-19.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart-dbgsym_4.1.21-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart-dbgsym_4.1.21-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart_4.1.21-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-rpart_4.1.21-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial-dbgsym_7.3-17-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial-dbgsym_7.3-17-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-spatial_7.3-17.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival-dbgsym_3.5-7-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival-dbgsym_3.5-7-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival_3.5-7-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-survival_3.5-7-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-doc-html_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-doc-info_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-doc-pdf_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib-dbgsym_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib-dbgsym_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib_4.3.2-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-mathlib_4.3.2-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-recommended_4.3.2-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-data_0.7.5-1~bullseyecran.0_all.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_amd64.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_i386.deb +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rmatrix_1.6-1.1-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rmatrix_1.6-1.1-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rmatrix_1.6-1.1.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpart_4.1.21-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpart_4.1.21-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpart_4.1.21.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12.orig.tar.gz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/survival_3.5-7-1~bullseyecran.0.debian.tar.xz +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/survival_3.5-7-1~bullseyecran.0.dsc +Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/survival_3.5-7.orig.tar.gz +Mirror `flat-src` has been updated successfully. +gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 +gpgv: Good signature from "Johannes Ranke " +gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC \ No newline at end of file diff --git a/system/t04_mirror/create.py b/system/t04_mirror/create.py index 111af25e..a2cdbf74 100644 --- a/system/t04_mirror/create.py +++ b/system/t04_mirror/create.py @@ -1,4 +1,6 @@ +from pathlib import Path import re +import os from lib import BaseTest @@ -7,7 +9,7 @@ class CreateMirror1Test(BaseTest): """ create mirror: all architectures + all components """ - runCmd = "aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" def check(self): self.check_output() @@ -18,7 +20,7 @@ class CreateMirror2Test(BaseTest): """ create mirror: all architectures and 1 component """ - runCmd = "aptly mirror create --ignore-signatures mirror2 http://cdn-fastly.deb.debian.org/debian/ stretch main" + runCmd = "aptly mirror create --ignore-signatures mirror2 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main" def check(self): self.check_output() @@ -29,7 +31,7 @@ class CreateMirror3Test(BaseTest): """ create mirror: some architectures and 2 components """ - runCmd = "aptly -architectures=i386,amd64 mirror create --ignore-signatures mirror3 http://cdn-fastly.deb.debian.org/debian/ stretch main contrib" + runCmd = "aptly -architectures=i386,amd64 mirror create --ignore-signatures mirror3 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main contrib" def check(self): self.check_output() @@ -42,7 +44,7 @@ class CreateMirror4Test(BaseTest): """ expectedCode = 1 - runCmd = "aptly -architectures=i386,amd64 mirror create --ignore-signatures mirror4 http://cdn-fastly.deb.debian.org/debian/ stretch life" + runCmd = "aptly -architectures=i386,amd64 mirror create --ignore-signatures mirror4 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch life" class CreateMirror5Test(BaseTest): @@ -51,7 +53,7 @@ class CreateMirror5Test(BaseTest): """ expectedCode = 1 - runCmd = "aptly -architectures=i386,nano68 mirror create --ignore-signatures mirror5 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly -architectures=i386,nano68 mirror create --ignore-signatures mirror5 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" class CreateMirror6Test(BaseTest): @@ -59,17 +61,17 @@ class CreateMirror6Test(BaseTest): create mirror: missing release """ expectedCode = 1 - requiresGPG1 = True + requiresGPG2 = True configOverride = {"max-tries": 1} - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror6 http://cdn-fastly.deb.debian.org/debian/ suslik" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror6 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ suslik" class CreateMirror7Test(BaseTest): """ create mirror: architectures fixed via config file """ - runCmd = "aptly mirror create --ignore-signatures mirror7 http://cdn-fastly.deb.debian.org/debian/ stretch main contrib" + runCmd = "aptly mirror create --ignore-signatures mirror7 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main contrib" configOverride = {"architectures": ["i386", "amd64"]} def check(self): @@ -82,9 +84,9 @@ class CreateMirror8Test(BaseTest): create mirror: already exists """ fixtureCmds = [ - "aptly mirror create --ignore-signatures mirror8 http://cdn-fastly.deb.debian.org/debian/ stretch main contrib" + "aptly mirror create --ignore-signatures mirror8 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main contrib" ] - runCmd = "aptly mirror create --ignore-signatures mirror8 http://cdn-fastly.deb.debian.org/debian/ stretch main contrib" + runCmd = "aptly mirror create --ignore-signatures mirror8 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main contrib" expectedCode = 1 @@ -92,9 +94,9 @@ class CreateMirror9Test(BaseTest): """ create mirror: repo with InRelease verification """ - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror9 http://cdn-fastly.deb.debian.org/debian/ stretch-backports" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror9 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch-backports" fixtureGpg = True - requiresGPG1 = True + requiresGPG2 = True def outputMatchPrepare(self, s): return re.sub(r'Signature made .* using|Warning: using insecure memory!\n', '', s) @@ -112,7 +114,7 @@ class CreateMirror10Test(BaseTest): """ create mirror: repo with InRelease verification, failure """ - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror10 http://cdn-fastly.deb.debian.org/debian/ stretch-backports" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror10 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch-backports" fixtureGpg = False gold_processor = BaseTest.expand_environ expectedCode = 1 @@ -126,7 +128,7 @@ class CreateMirror11Test(BaseTest): create mirror: repo with Release + Release.gpg verification """ configOverride = {"max-tries": 1} - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror11 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror11 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" fixtureGpg = True def outputMatchPrepare(self, s): @@ -142,7 +144,7 @@ class CreateMirror12Test(BaseTest): create mirror: repo with Release+Release.gpg verification, failure """ configOverride = {"max-tries": 1} - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror12 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror12 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" fixtureGpg = False gold_processor = BaseTest.expand_environ expectedCode = 1 @@ -155,7 +157,7 @@ class CreateMirror13Test(BaseTest): """ create mirror: skip verification using config file """ - runCmd = "aptly mirror create mirror13 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly mirror create mirror13 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" configOverride = {"gpgDisableVerify": True} def check(self): @@ -167,7 +169,7 @@ class CreateMirror14Test(BaseTest): """ create mirror: flat repository """ - runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror14 https://cloud.r-project.org/bin/linux/debian jessie-cran35/" + runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror14 http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian bullseye-cran40/" fixtureGpg = True def outputMatchPrepare(self, s): @@ -186,7 +188,7 @@ class CreateMirror15Test(BaseTest): """ create mirror: flat repository + components """ - runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror14 https://cloud.r-project.org/bin/linux/debian jessie-cran35/ main" + runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror14 http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian bullseye-cran40/ main" expectedCode = 1 @@ -196,14 +198,14 @@ class CreateMirror16Test(BaseTest): """ expectedCode = 1 - runCmd = "aptly -architectures=source mirror create -ignore-signatures mirror16 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly -architectures=source mirror create -ignore-signatures mirror16 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" class CreateMirror17Test(BaseTest): """ create mirror: mirror with sources enabled """ - runCmd = "aptly -architectures=i386 mirror create -ignore-signatures -with-sources mirror17 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly -architectures=i386 mirror create -ignore-signatures -with-sources mirror17 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" def check(self): self.check_output() @@ -221,7 +223,11 @@ class CreateMirror18Test(BaseTest): "ppaCodename": "maverick", } - runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror18 ppa:gladky-anton/gnuplot" + fixtureCmds = [ + "gpg --no-default-keyring --keyring=ppa.gpg --keyserver=hkp://keyserver.ubuntu.com:80 --recv-keys 5BFCD481D86D5824470E469F9000B1C3A01F726C 02219381E9161C78A46CB2BFA5279A973B1F56C0", + f"chmod 400 {os.path.join(os.environ['HOME'], '.gnupg/ppa.gpg')}" + ] + runCmd = "aptly mirror create -keyring=ppa.gpg mirror18 ppa:gladky-anton/gnuplot" def outputMatchPrepare(self, s): return re.sub(r'Signature made .* using', '', s) @@ -237,7 +243,7 @@ class CreateMirror19Test(BaseTest): """ fixtureGpg = True - runCmd = "aptly -architectures='i386' mirror create -keyring=aptlytest.gpg -with-sources mirror19 http://security.debian.org/ stretch/updates main" + runCmd = "aptly -architectures='i386' mirror create -keyring=aptlytest.gpg -with-sources mirror19 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" def outputMatchPrepare(self, s): return re.sub(r'Signature made .* using', '', s) @@ -258,7 +264,7 @@ class CreateMirror20Test(BaseTest): fixtureGpg = True configOverride = {"max-tries": 1} - runCmd = "aptly -architectures='i386' mirror create -keyring=aptlytest.gpg -with-sources mirror20 http://security.debian.org/ stretch/updates main" + runCmd = "aptly -architectures='i386' mirror create -keyring=aptlytest.gpg -with-sources mirror20 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" environmentOverride = {"HTTP_PROXY": "127.0.0.1:3137"} expectedCode = 1 @@ -270,8 +276,8 @@ class CreateMirror20Test(BaseTest): ).replace( 'proxyconnect tcp', 'http: error connecting to proxy http://127.0.0.1:3137' ).replace( - 'Get http://security.debian.org/dists/stretch/updates/Release:', - 'Get "http://security.debian.org/dists/stretch/updates/Release":' + 'Get http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release:', + 'Get "http://repo.aptly.info/system-tests/archive.debian.org/debian-security/dists/stretch/updates/Release":' ) @@ -280,7 +286,7 @@ class CreateMirror21Test(BaseTest): create mirror: flat repository in subdir """ configOverride = {"max-tries": 1} - runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror21 http://pkg.jenkins-ci.org/debian-stable binary/" + runCmd = "aptly mirror create -keyring=aptlytest.gpg mirror21 http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable binary/" fixtureGpg = True def outputMatchPrepare(self, s): @@ -302,7 +308,7 @@ class CreateMirror22Test(BaseTest): """ create mirror: mirror with filter """ - runCmd = "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' mirror22 http://security.debian.org/ stretch/updates main" + runCmd = "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' mirror22 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" def check(self): def removeDates(s): @@ -317,7 +323,7 @@ class CreateMirror23Test(BaseTest): """ create mirror: mirror with wrong filter """ - runCmd = "aptly mirror create -ignore-signatures -filter='nginx | ' mirror23 http://security.debian.org/ stretch/updates main" + runCmd = "aptly mirror create -ignore-signatures -filter='nginx | ' mirror23 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" expectedCode = 1 @@ -325,7 +331,7 @@ class CreateMirror24Test(BaseTest): """ create mirror: disable config value with option """ - runCmd = "aptly mirror create -ignore-signatures=false -keyring=aptlytest.gpg mirror24 http://security.debian.org/ stretch/updates main" + runCmd = "aptly mirror create -ignore-signatures=false -keyring=aptlytest.gpg mirror24 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" fixtureGpg = True def outputMatchPrepare(self, s): @@ -340,7 +346,7 @@ class CreateMirror25Test(BaseTest): """ create mirror: mirror with udebs enabled """ - runCmd = "aptly -architectures=i386 mirror create -ignore-signatures -with-udebs mirror25 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly -architectures=i386 mirror create -ignore-signatures -with-udebs mirror25 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" def check(self): self.check_output() @@ -351,7 +357,7 @@ class CreateMirror26Test(BaseTest): """ create mirror: flat mirror with udebs """ - runCmd = "aptly mirror create -keyring=aptlytest.gpg -with-udebs mirror26 http://pkg.jenkins-ci.org/debian-stable binary/" + runCmd = "aptly mirror create -keyring=aptlytest.gpg -with-udebs mirror26 http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable binary/" fixtureGpg = True expectedCode = 1 @@ -360,7 +366,7 @@ class CreateMirror27Test(BaseTest): """ create mirror: component with slashes, no stripping """ - runCmd = "aptly mirror create --ignore-signatures mirror27 https://mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu wheezy openmanage/740" + runCmd = "aptly mirror create --ignore-signatures mirror27 http://repo.aptly.info/system-tests/mirror.chpc.utah.edu/pub/linux.dell.com/repo/community/ubuntu wheezy openmanage/740" def outputMatchPrepare(self, s): return self.strip_retry_lines(s) @@ -374,19 +380,24 @@ class CreateMirror29Test(BaseTest): """ create mirror: repo with InRelease verification (internal GPG implementation) """ - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror9 http://cdn-fastly.deb.debian.org/debian/ stretch-backports" + fixtureCmds = ["gpg --no-default-keyring --keyring aptlytest.gpg --export-options export-minimal --export -o " + os.path.join( + os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")] + runCmd = "aptly mirror create --keyring=aptlytest-gpg1.gpg mirror9 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch-backports" configOverride = {"gpgProvider": "internal"} fixtureGpg = True def outputMatchPrepare(self, s): return re.sub(r'Signature made .* using', '', s) + def teardown(self): + self.run_cmd(["rm", "-f", os.path.join(os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")]) + class CreateMirror30Test(BaseTest): """ create mirror: repo with InRelease verification, failure (internal GPG implementation) """ - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror10 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror10 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" configOverride = {"gpgProvider": "internal", "max-tries": 1} gold_processor = BaseTest.expand_environ fixtureGpg = False @@ -400,20 +411,25 @@ class CreateMirror31Test(BaseTest): """ create mirror: repo with Release + Release.gpg verification (internal GPG implementation) """ - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror11 http://cdn-fastly.deb.debian.org/debian/ stretch" + fixtureCmds = ["gpg --no-default-keyring --keyring aptlytest.gpg --export-options export-minimal --export -o " + os.path.join( + os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")] + runCmd = "aptly mirror create --keyring=aptlytest-gpg1.gpg mirror11 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" configOverride = {"gpgProvider": "internal", "max-tries": 1} fixtureGpg = True def outputMatchPrepare(self, s): return re.sub(r'Signature made .* using', '', s) + def teardown(self): + self.run_cmd(["rm", "-f", os.path.join(os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")]) + class CreateMirror32Test(BaseTest): """ create mirror: repo with Release + Release.gpg verification (gpg2) """ configOverride = {"max-tries": 1} - runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror32 http://cdn-fastly.deb.debian.org/debian/ stretch" + runCmd = "aptly mirror create --keyring=aptlytest.gpg mirror32 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch" fixtureGpg = True requiresGPG2 = True @@ -425,3 +441,94 @@ class CreateMirror32Test(BaseTest): def check(self): self.check_output() self.check_cmd_output("aptly mirror show mirror32", "mirror_show") + + +class CreateMirror33Test(BaseTest): + """ + create mirror: repo with only InRelease file but no verification + """ + configOverride = {"max-tries": 1} + runCmd = "aptly mirror create -ignore-signatures mirror33 http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64 ./" + fixtureGpg = False + requiresGPG2 = False + + def check(self): + self.check_output() + self.check_cmd_output("aptly mirror show mirror33", "mirror_show") + + +class CreateMirror34Test(BaseTest): + """ + create mirror error: flat repo with filter but no architectures in InRelease file + """ + configOverride = {"max-tries": 1} + runCmd = "aptly mirror create -ignore-signatures -filter \"cuda-12-6 (= 12.6.2-1)\" -filter-with-deps mirror34 http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ ./" + + +class CreateMirror35Test(BaseTest): + """ + create mirror: flat repo with filter but no architectures in InRelease file + """ + configOverride = {"max-tries": 1} + fixtureCmds = [ + "aptly mirror create -architectures amd64 -ignore-signatures -filter \"cuda-12-6 (= 12.6.2-1)\" -filter-with-deps mirror35 " + "http://repo.aptly.info/system-tests/developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ ./", + ] + runCmd = "aptly mirror update -ignore-signatures mirror35" + + # the downloading of the actual packages will return 404 since they don't exist. ignore the errors, the test verifies proper count of filtered packages + + def outputMatchPrepare(self, s): + s = re.sub(r'Downloading: .*\n', '', s, flags=re.MULTILINE) + s = re.sub(r'Download Error: .*\n', '', s, flags=re.MULTILINE) + s = re.sub(r'Retrying .*\n', '', s, flags=re.MULTILINE) + s = re.sub(r'Error \(retrying\): .*\n', '', s, flags=re.MULTILINE) + s = re.sub(r'HTTP code 404 while fetching .*\n', '', s, flags=re.MULTILINE) + s = re.sub(r'ERROR: unable to update: .*\n', '', s, flags=re.MULTILINE) + return s + + def check(self): + self.check_output() + self.check_cmd_output("aptly mirror show mirror35", "mirror_show") + + +class CreateMirror36Test(BaseTest): + """ + create mirror: mirror with filter read from file + """ + filterFilePath = os.path.join(os.environ["HOME"], ".aptly-filter.tmp") + fixtureCmds = [f"bash -c \"echo -n 'nginx | Priority (required)' > {filterFilePath}\""] + runCmd = f"aptly mirror create -ignore-signatures -filter='@{filterFilePath}' mirror36 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" + + def check(self): + def removeDates(s): + return re.sub(r"(Date|Valid-Until): [,0-9:+A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly mirror show mirror36", + "mirror_show", match_prepare=removeDates) + + +class CreateMirror37Test(BaseTest): + """ + create mirror: mirror with filter read from stdin + """ + aptly_testing_bin = Path(__file__).parent.parent.parent / "aptly.test" + # Hack: Normally the test system detects if runCmd is an aptly command and then + # substitutes the aptly_testing_bin path and deletes the last three lines of output. + # However, I need to run it in bash to control stdin, so I have to do it manually. + runCmd = [ + "bash", + "-c", + f"echo -n 'nginx | Priority (required)' | {aptly_testing_bin} mirror create " + + "-ignore-signatures -filter='@-' mirror37 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main " + + "| grep -vE '^(EXIT|PASS|coverage:)'" + ] + + def check(self): + def removeDates(s): + return re.sub(r"(Date|Valid-Until): [,0-9:+A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly mirror show mirror37", + "mirror_show", match_prepare=removeDates) diff --git a/system/t04_mirror/drop.py b/system/t04_mirror/drop.py index 36a16f6d..99248a82 100644 --- a/system/t04_mirror/drop.py +++ b/system/t04_mirror/drop.py @@ -6,7 +6,7 @@ class DropMirror1Test(BaseTest): drop mirror: regular list """ fixtureCmds = [ - "aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch", + "aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch", ] runCmd = "aptly mirror drop mirror1" diff --git a/system/t04_mirror/edit.py b/system/t04_mirror/edit.py index ad274a68..09a5b18f 100644 --- a/system/t04_mirror/edit.py +++ b/system/t04_mirror/edit.py @@ -1,4 +1,5 @@ import re + from lib import BaseTest @@ -48,7 +49,7 @@ class EditMirror5Test(BaseTest): edit mirror: remove filter """ fixtureCmds = [ - "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' mirror5 http://security.debian.org/ stretch/updates main", + "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' mirror5 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main", ] runCmd = "aptly mirror edit -filter= mirror5" @@ -65,7 +66,7 @@ class EditMirror6Test(BaseTest): edit mirror: change architectures """ fixtureCmds = [ - "aptly mirror create -ignore-signatures -architectures=amd64 mirror6 http://cdn-fastly.deb.debian.org/debian stretch main" + "aptly mirror create -ignore-signatures -architectures=amd64 mirror6 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian stretch main" ] runCmd = "aptly mirror edit -ignore-signatures -architectures=amd64,i386 mirror6" @@ -79,7 +80,7 @@ class EditMirror7Test(BaseTest): edit mirror: change architectures to missing archs """ fixtureCmds = [ - "aptly mirror create -ignore-signatures -architectures=amd64 stretch http://cdn-fastly.deb.debian.org/debian stretch main" + "aptly mirror create -ignore-signatures -architectures=amd64 stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian stretch main" ] runCmd = "aptly mirror edit -ignore-signatures -architectures=amd64,x56 stretch" expectedCode = 1 @@ -101,7 +102,7 @@ class EditMirror9Test(BaseTest): """ edit mirror: flat mirror with udebs """ - fixtureCmds = ["aptly mirror create -keyring=aptlytest.gpg mirror9 http://pkg.jenkins-ci.org/debian-stable binary/"] + fixtureCmds = ["aptly mirror create -keyring=aptlytest.gpg mirror9 http://repo.aptly.info/system-tests/pkg.jenkins.io/debian-stable binary/"] fixtureGpg = True runCmd = "aptly mirror edit -with-udebs mirror9" expectedCode = 1 @@ -111,6 +112,5 @@ class EditMirror10Test(BaseTest): """ edit mirror: change archive url """ - requiresFTP = True - fixtureCmds = ["aptly mirror create -ignore-signatures mirror10 ftp://ftp.ru.debian.org/debian stretch main"] - runCmd = "aptly mirror edit -ignore-signatures -archive-url ftp://ftp.ch.debian.org/debian mirror10" + fixtureCmds = ["aptly mirror create -ignore-signatures mirror10 http://repo.aptly.info/system-tests/ftp.ru.debian.org/debian bookworm main"] + runCmd = "aptly mirror edit -ignore-signatures -archive-url http://repo.aptly.info/system-tests/ftp.ch.debian.org/debian mirror10" diff --git a/system/t04_mirror/list.py b/system/t04_mirror/list.py index 7b02e288..2422b3c0 100644 --- a/system/t04_mirror/list.py +++ b/system/t04_mirror/list.py @@ -1,16 +1,17 @@ -from lib import BaseTest import re +from lib import BaseTest + class ListMirror1Test(BaseTest): """ list mirrors: regular list """ fixtureCmds = [ - "aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch", - "aptly mirror create -with-sources --ignore-signatures mirror2 http://cdn-fastly.deb.debian.org/debian/ stretch contrib", - "aptly -architectures=i386 mirror create --ignore-signatures mirror3 http://cdn-fastly.deb.debian.org/debian/ stretch non-free", - "aptly mirror create -ignore-signatures mirror4 http://download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/ ./", + "aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch", + "aptly mirror create -with-sources --ignore-signatures mirror2 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch contrib", + "aptly -architectures=i386 mirror create --ignore-signatures mirror3 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch non-free", + "aptly mirror create -ignore-signatures mirror4 http://repo.aptly.info/system-tests/download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/ ./", ] runCmd = "aptly mirror list" @@ -49,10 +50,10 @@ class ListMirror6Test(BaseTest): list mirrors: regular list """ fixtureCmds = [ - "aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch", - "aptly mirror create -with-sources --ignore-signatures mirror2 http://cdn-fastly.deb.debian.org/debian/ stretch contrib", - "aptly -architectures=i386 mirror create --ignore-signatures mirror3 http://cdn-fastly.deb.debian.org/debian/ stretch non-free", - "aptly mirror create -ignore-signatures mirror4 http://download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/ ./", + "aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch", + "aptly mirror create -with-sources --ignore-signatures mirror2 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch contrib", + "aptly -architectures=i386 mirror create --ignore-signatures mirror3 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch non-free", + "aptly mirror create -ignore-signatures mirror4 http://repo.aptly.info/system-tests/download.opensuse.org/repositories/Apache:/MirrorBrain/Debian_9.0/ ./", ] runCmd = "aptly mirror list -json" diff --git a/system/t04_mirror/show.py b/system/t04_mirror/show.py index ce1d25d4..98e7cbb2 100644 --- a/system/t04_mirror/show.py +++ b/system/t04_mirror/show.py @@ -1,12 +1,13 @@ -from lib import BaseTest import re +from lib import BaseTest + class ShowMirror1Test(BaseTest): """ show mirror: regular mirror """ - fixtureCmds = ["aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch"] + fixtureCmds = ["aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch"] runCmd = "aptly mirror show mirror1" @@ -34,19 +35,19 @@ class ShowMirror4Test(BaseTest): show mirror: mirror with filter """ fixtureCmds = [ - "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' -filter-with-deps=true mirror4 http://security.debian.org/ stretch/updates main" + "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' -filter-with-deps=true mirror4 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" ] runCmd = "aptly mirror show mirror4" def outputMatchPrepare(self, s): - return re.sub(r"(Date|Valid-Until): [,0-9:+A-Za-z -]+\n", "", s) + return re.sub(r"(Date): [,0-9:+A-Za-z -]+\n", "", s) class ShowMirror5Test(BaseTest): """ show mirror: regular mirror """ - fixtureCmds = ["aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch"] + fixtureCmds = ["aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch"] runCmd = "aptly mirror show -json mirror1" def outputMatchPrepare(_, s): @@ -74,7 +75,7 @@ class ShowMirror8Test(BaseTest): show mirror: mirror with filter """ fixtureCmds = [ - "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' -filter-with-deps=true mirror4 http://security.debian.org/ stretch/updates main" + "aptly mirror create -ignore-signatures -filter='nginx | Priority (required)' -filter-with-deps=true mirror4 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" ] runCmd = "aptly mirror show -json mirror4" diff --git a/system/t04_mirror/update.py b/system/t04_mirror/update.py index 1e99d4b7..fb44dd7e 100644 --- a/system/t04_mirror/update.py +++ b/system/t04_mirror/update.py @@ -1,8 +1,9 @@ -import string -import re -import os -import shutil import inspect +import os +import re +import shutil +import string + from lib import BaseTest @@ -21,7 +22,7 @@ class UpdateMirror1Test(BaseTest): sortOutput = True longTest = False fixtureCmds = [ - "aptly -architectures=i386,amd64 mirror create --ignore-signatures varnish https://packagecloud.io/varnishcache/varnish30/debian/ wheezy main", + "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 varnish" outputMatchPrepare = filterOutRedirects @@ -114,7 +115,7 @@ class UpdateMirror7Test(BaseTest): sortOutput = True fixtureGpg = True fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg -architectures=amd64 flat https://cloud.r-project.org/bin/linux/debian jessie-cran35/", + "aptly mirror create --keyring=aptlytest.gpg -architectures=amd64 flat http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian bullseye-cran40/", ] runCmd = "aptly mirror update --keyring=aptlytest.gpg flat" outputMatchPrepare = filterOutSignature @@ -129,7 +130,7 @@ class UpdateMirror8Test(BaseTest): fixtureGpg = True fixturePool = True fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg gnuplot-maverick-src http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick", + "aptly mirror create --keyring=aptlytest.gpg gnuplot-maverick-src http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick", ] runCmd = "aptly mirror update --keyring=aptlytest.gpg gnuplot-maverick-src" outputMatchPrepare = filterOutSignature @@ -142,7 +143,7 @@ class UpdateMirror9Test(BaseTest): sortOutput = True fixtureGpg = True fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg -with-sources flat-src https://cloud.r-project.org/bin/linux/debian jessie-cran35/", + "aptly mirror create --keyring=aptlytest.gpg -with-sources flat-src http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian bullseye-cran40/", ] runCmd = "aptly mirror update --keyring=aptlytest.gpg flat-src" outputMatchPrepare = filterOutSignature @@ -155,13 +156,13 @@ class UpdateMirror10Test(BaseTest): sortOutput = True fixtureGpg = True fixtureCmds = [ - "aptly mirror create -keyring=aptlytest.gpg -with-sources -filter='!(Name (% r-*)), !($$PackageType (source))' flat-src https://cloud.r-project.org/bin/linux/debian jessie-cran35/", + "aptly mirror create -keyring=aptlytest.gpg -with-sources -filter='!(Name (% r-*)), !($$PackageType (source))' flat-src http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian bullseye-cran40/", ] runCmd = "aptly mirror update --keyring=aptlytest.gpg flat-src" outputMatchPrepare = filterOutSignature -class UpdateMirror11Test(BaseTest): +class UpdateMirror11FTPTest(BaseTest): """ update mirrors: update over FTP """ @@ -172,7 +173,7 @@ class UpdateMirror11Test(BaseTest): requiresFTP = True fixtureCmds = [ "aptly mirror create -keyring=aptlytest.gpg -filter='Priority (required), Name (% s*)' " - "-architectures=i386 stretch-main https://snapshot.debian.org/archive/debian/20220201T025006Z/ stretch main", + "-architectures=i386 stretch-main http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/ stretch main", ] outputMatchPrepare = filterOutSignature runCmd = "aptly mirror update -keyring=aptlytest.gpg stretch-main" @@ -187,7 +188,7 @@ class UpdateMirror12Test(BaseTest): longTest = False fixtureGpg = True fixtureCmds = [ - "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (gnupg2)' -with-udebs stretch http://cdn-fastly.deb.debian.org/debian/ stretch main non-free", + "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (gnupg2)' -with-udebs stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main non-free", ] runCmd = "aptly mirror update -keyring=aptlytest.gpg stretch" outputMatchPrepare = filterOutSignature @@ -200,7 +201,7 @@ class UpdateMirror13Test(BaseTest): sortOutput = True longTest = False fixtureCmds = [ - "aptly -architectures=i386,amd64 mirror create --ignore-signatures varnish https://packagecloud.io/varnishcache/varnish30/debian/ wheezy main", + "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 --skip-existing-packages varnish" outputMatchPrepare = filterOutRedirects @@ -213,7 +214,7 @@ class UpdateMirror14Test(BaseTest): sortOutput = True longTest = False fixtureCmds = [ - "aptly -architectures=i386,amd64 mirror create --ignore-signatures varnish https://packagecloud.io/varnishcache/varnish30/debian/ wheezy main", + "aptly -architectures=i386,amd64 mirror create --ignore-signatures varnish http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/ wheezy main", "aptly mirror update --ignore-signatures --skip-existing-packages varnish" ] runCmd = "aptly mirror update --ignore-signatures --skip-existing-packages varnish" @@ -281,7 +282,7 @@ class UpdateMirror17Test(BaseTest): sortOutput = True longTest = False fixtureCmds = [ - "aptly mirror create -ignore-signatures -architectures=i386 -filter=libboost-program-options-dev stretch http://cdn-fastly.deb.debian.org/debian stretch main", + "aptly mirror create -ignore-signatures -architectures=i386 -filter=libboost-program-options-dev stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian stretch main", ] runCmd = "aptly mirror update -ignore-signatures stretch" @@ -308,7 +309,7 @@ class UpdateMirror18Test(BaseTest): sortOutput = True longTest = False fixtureCmds = [ - "aptly mirror create -ignore-signatures -architectures=i386 -filter=libboost-program-options-dev stretch http://cdn-fastly.deb.debian.org/debian stretch main", + "aptly mirror create -ignore-signatures -architectures=i386 -filter=libboost-program-options-dev stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian stretch main", ] runCmd = "aptly mirror update -ignore-signatures stretch" configOverride = {'skipLegacyPool': True} @@ -337,7 +338,7 @@ class UpdateMirror19Test(BaseTest): longTest = False fixtureGpg = True fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg pagerduty https://packages.pagerduty.com/pdagent deb/" + "aptly mirror create --keyring=aptlytest.gpg pagerduty http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent deb/" ] runCmd = "aptly mirror update --keyring=aptlytest.gpg pagerduty" outputMatchPrepare = filterOutSignature @@ -352,13 +353,18 @@ class UpdateMirror20Test(BaseTest): """ sortOutput = True fixtureGpg = True - fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg -architectures=amd64 --filter='r-cran-class' flat https://cloud.r-project.org/bin/linux/debian jessie-cran35/", - ] configOverride = {"gpgProvider": "internal"} - runCmd = "aptly mirror update --keyring=aptlytest.gpg flat" + fixtureCmds = [ + "gpg --no-default-keyring --keyring aptlytest.gpg --export -o " + os.path.join( + os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg"), + "aptly mirror create --keyring=aptlytest-gpg1.gpg -architectures=amd64 --filter='r-cran-class' flat http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian bullseye-cran40/", + ] + runCmd = "aptly mirror update --keyring=aptlytest-gpg1.gpg flat" outputMatchPrepare = filterOutSignature + def teardown(self): + self.run_cmd(["rm", "-f", os.path.join(os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")]) + class UpdateMirror21Test(BaseTest): """ @@ -368,14 +374,19 @@ class UpdateMirror21Test(BaseTest): configOverride = {"gpgProvider": "internal", "max-tries": 1} fixtureGpg = True fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg pagerduty https://packages.pagerduty.com/pdagent deb/" + "gpg --no-default-keyring --keyring aptlytest.gpg --export -o " + os.path.join( + os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg"), + "aptly mirror create --keyring=aptlytest-gpg1.gpg pagerduty http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent deb/" ] - runCmd = "aptly mirror update --keyring=aptlytest.gpg pagerduty" + runCmd = "aptly mirror update --keyring=aptlytest-gpg1.gpg pagerduty" outputMatchPrepare = filterOutSignature def output_processor(self, output): return "\n".join(line for line in self.ensure_utf8(output).split("\n") if ".deb" not in line) + def teardown(self): + self.run_cmd(["rm", "-f", os.path.join(os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")]) + class UpdateMirror22Test(BaseTest): """ @@ -384,13 +395,18 @@ class UpdateMirror22Test(BaseTest): configOverride = {"gpgProvider": "internal"} fixtureGpg = True fixtureCmds = [ - "aptly mirror create --keyring=aptlytest.gpg --filter=nomatch libnvidia-container https://nvidia.github.io/libnvidia-container/ubuntu16.04/amd64 ./" + "gpg --no-default-keyring --keyring aptlytest.gpg --export -o " + os.path.join( + os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg"), + "aptly mirror create --keyring=aptlytest-gpg1.gpg --filter=nomatch libnvidia-container http://repo.aptly.info/system-tests/nvidia.github.io/libnvidia-container/stable/ubuntu16.04/amd64 ./" ] - runCmd = "aptly mirror update --keyring=aptlytest.gpg libnvidia-container" + runCmd = "aptly mirror update --keyring=aptlytest-gpg1.gpg libnvidia-container" def outputMatchPrepare(self, s): return re.sub(r'Signature made .* using|Packages filtered: .* -> 0.', '', s) + def teardown(self): + self.run_cmd(["rm", "-f", os.path.join(os.environ["HOME"], ".gnupg/aptlytest-gpg1.gpg")]) + class UpdateMirror23Test(BaseTest): """ @@ -401,7 +417,7 @@ class UpdateMirror23Test(BaseTest): longTest = False fixtureGpg = True fixtureCmds = [ - "aptly -architectures=s390x mirror create -keyring=aptlytest.gpg -filter='installer' -with-installer stretch http://cdn-fastly.deb.debian.org/debian/ stretch main non-free", + "aptly -architectures=s390x mirror create -keyring=aptlytest.gpg -filter='installer' -with-installer stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main non-free", ] runCmd = "aptly mirror update -keyring=aptlytest.gpg stretch" outputMatchPrepare = filterOutSignature @@ -416,7 +432,53 @@ class UpdateMirror24Test(BaseTest): longTest = False fixtureGpg = True fixtureCmds = [ - "aptly -architectures=amd64 mirror create -keyring=aptlytest.gpg -filter='installer' -with-installer trusty http://us.archive.ubuntu.com/ubuntu/ trusty main restricted", + "aptly -architectures=amd64 mirror create -keyring=aptlytest.gpg -filter='installer' -with-installer trusty http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/ trusty main restricted", ] runCmd = "aptly mirror update -keyring=aptlytest.gpg trusty" outputMatchPrepare = filterOutSignature + + +class UpdateMirror25Test(BaseTest): + """ + update mirrors: mirror with / in distribution + """ + configOverride = {"max-tries": 1} + sortOutput = True + longTest = False + fixtureGpg = True + fixtureCmds = [ + "aptly -architectures='i386' mirror create -keyring=aptlytest.gpg -with-sources mirror19 http://repo.aptly.info/system-tests/archive.debian.org/debian-security/ stretch/updates main" + ] + runCmd = "aptly mirror update -keyring=aptlytest.gpg mirror19" + outputMatchPrepare = filterOutSignature + + +class UpdateMirror26Test(BaseTest): + """ + update mirrors: regular update, grab downloader + """ + configOverride = {"downloader": "grab"} + sortOutput = True + longTest = False + fixtureGpg = True + fixtureCmds = [ + "aptly mirror create -architectures=i386 -keyring=aptlytest.gpg -filter=libboost-program-options-dev grab http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian stretch main", + ] + runCmd = "aptly mirror update -downloader=grab -keyring=aptlytest.gpg grab" + outputMatchPrepare = filterOutRedirects + + +class UpdateMirror27Test(BaseTest): + """ + update mirrors: failing update, grab downloader + """ + configOverride = {"downloader": "grab"} + sortOutput = True + longTest = False + fixtureGpg = True + fixtureCmds = [ + "aptly mirror create -architectures=amd64,i386 -keyring=aptlytest.gpg -filter=libboost-program-options-dev grab-fail http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian stretch main", + ] + runCmd = "aptly mirror update -downloader=grab -keyring=aptlytest.gpg grab-fail" + outputMatchPrepare = filterOutRedirects + expectedCode = 1 diff --git a/system/t05_snapshot/VerifySnapshot4Test_gold b/system/t05_snapshot/VerifySnapshot4Test_gold index 1d6c02ce..3d5499ab 100644 --- a/system/t05_snapshot/VerifySnapshot4Test_gold +++ b/system/t05_snapshot/VerifySnapshot4Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (1737): +Missing dependencies (1741): 915resolution [i386] 9fonts [i386] abakus [i386] @@ -74,6 +74,7 @@ Missing dependencies (1737): big-blast [i386] bigdft [i386] bigsdb [i386] + bind [i386] bioclipse [i386] bioimagesuite [i386] bioimagexd [i386] @@ -204,6 +205,7 @@ Missing dependencies (1737): elmer-doc [i386] elph [i386] emacs-wiki [i386] + emacs22 [i386] emacs23-common-non-dfsg [i386] emacspeak-ss [i386] embassy [i386] @@ -574,6 +576,7 @@ Missing dependencies (1737): kbackgammon [i386] kbdcontrol [i386] kchart [i386] + kde [i386] kde-icons-crystal [i386] kde-icons-oxygen [i386] kde-l10n (>= 4:4.8.4) [i386] @@ -688,7 +691,6 @@ Missing dependencies (1737): libdeal.ii-dev [i386] libdigest-cmac-perl [i386] libdrmaa1.0 [i386] - libdspam7-drv (= 3.10.1+dfsg-11) [i386] libdvdcss [i386] libdvdcss-dev [i386] libdvdcss2 [i386] @@ -1641,6 +1643,7 @@ Missing dependencies (1737): ttf-larabie-deco [i386] ttf-larabie-straight [i386] ttf-mscorefonts-installer [i386] + ttf-thryomanes [i386] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [i386] ttf2pt1 [i386] tuxpaint (= 1:0.9.21) [i386] @@ -1722,6 +1725,7 @@ Missing dependencies (1737): xmind [i386] xnat [i386] xorsa [i386] + xpdf-reader [i386] xsidplay [i386] xtranslate [i386] xul-ext-gnome-keyring [i386] diff --git a/system/t05_snapshot/VerifySnapshot5Test_gold b/system/t05_snapshot/VerifySnapshot5Test_gold index 39e0af1c..9f14d4f5 100644 --- a/system/t05_snapshot/VerifySnapshot5Test_gold +++ b/system/t05_snapshot/VerifySnapshot5Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (1617): +Missing dependencies (1621): 915resolution [i386] 9fonts [i386] abakus [i386] @@ -71,6 +71,7 @@ Missing dependencies (1617): big-blast [i386] bigdft [i386] bigsdb [i386] + bind [i386] bioclipse [i386] bioimagesuite [i386] bioimagexd [i386] @@ -187,6 +188,7 @@ Missing dependencies (1617): elexis [i386] elph [i386] emacs-wiki [i386] + emacs22 [i386] emacspeak-ss [i386] embassy [i386] embassy-phylip [i386] @@ -513,6 +515,7 @@ Missing dependencies (1617): kbackgammon [i386] kbdcontrol [i386] kchart [i386] + kde [i386] kde-icons-crystal [i386] kde-icons-oxygen [i386] kde-l10n (>= 4:4.8.4) [i386] @@ -624,7 +627,6 @@ Missing dependencies (1617): libdeal.ii-dev [i386] libdigest-cmac-perl [i386] libdrmaa1.0 [i386] - libdspam7-drv (= 3.10.1+dfsg-11) [i386] libdvdcss [i386] libdvdcss-dev [i386] libdvdcss2 [i386] @@ -1532,6 +1534,7 @@ Missing dependencies (1617): tripal [i386] trnascan-se [i386] tscope [i386] + ttf-thryomanes [i386] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [i386] ttf2pt1 [i386] tuxpaint (= 1:0.9.21) [i386] @@ -1603,6 +1606,7 @@ Missing dependencies (1617): xmind [i386] xnat [i386] xorsa [i386] + xpdf-reader [i386] xsidplay [i386] xtranslate [i386] xul-ext-gnome-keyring [i386] diff --git a/system/t05_snapshot/VerifySnapshot6Test_gold b/system/t05_snapshot/VerifySnapshot6Test_gold index f4083476..b8574e7d 100644 --- a/system/t05_snapshot/VerifySnapshot6Test_gold +++ b/system/t05_snapshot/VerifySnapshot6Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (3781): +Missing dependencies (3787): 915resolution [amd64] 915resolution [i386] 9fonts [amd64] @@ -51,6 +51,8 @@ Missing dependencies (3781): angband-audio [i386] aolserver-doc (>= 4.0.1-1) [amd64] aolserver-doc (>= 4.0.1-1) [i386] + apache [amd64] + apache [i386] apache-ssl [amd64] apache-ssl [i386] ape [amd64] @@ -159,6 +161,8 @@ Missing dependencies (3781): bigdft [i386] bigsdb [amd64] bigsdb [i386] + bind [amd64] + bind [i386] bioclipse [amd64] bioclipse [i386] bioimagesuite [amd64] @@ -454,6 +458,8 @@ Missing dependencies (3781): elph [i386] emacs-wiki [amd64] emacs-wiki [i386] + emacs22 [amd64] + emacs22 [i386] emacs23-common-non-dfsg [amd64] emacs23-common-non-dfsg [i386] emacspeak-ss [amd64] @@ -1207,8 +1213,6 @@ Missing dependencies (3781): jabber [i386] jamnntpd [amd64] jamnntpd [i386] - java-gcj-compat (>= 1.0.69) [amd64] - java-gcj-compat (>= 1.0.69) [i386] jbibtex [amd64] jbibtex [i386] jbibtex-bin [amd64] @@ -1258,6 +1262,8 @@ Missing dependencies (3781): kbdcontrol [i386] kchart [amd64] kchart [i386] + kde [amd64] + kde [i386] kde-i18n-he [amd64] kde-i18n-he [i386] kde-icons-crystal [amd64] @@ -1518,8 +1524,6 @@ Missing dependencies (3781): libdigest-cmac-perl [i386] libdrmaa1.0 [amd64] libdrmaa1.0 [i386] - libdspam7-drv (= 3.10.1+dfsg-11) [amd64] - libdspam7-drv (= 3.10.1+dfsg-11) [i386] libdvdcss [amd64] libdvdcss [i386] libdvdcss-dev [amd64] @@ -1598,8 +1602,6 @@ Missing dependencies (3781): libint [i386] libjai-imageio-core-java [amd64] libjai-imageio-core-java [i386] - libjakarta-poi-java (>= 3.2) [amd64] - libjakarta-poi-java (>= 3.2) [i386] libjavascript-perl [amd64] libjavascript-perl [i386] libjdom-java [amd64] @@ -2184,6 +2186,8 @@ Missing dependencies (3781): mesquite [i386] metadisorder [amd64] metadisorder [i386] + metamail [amd64] + metamail [i386] metarep [amd64] metarep [i386] mga-vid-module [amd64] @@ -2301,8 +2305,6 @@ Missing dependencies (3781): mummergpu [i386] murasaki [amd64] murasaki [i386] - musixlyr (>= 1.10-4) [amd64] - musixlyr (>= 1.10-4) [i386] musmap [amd64] musmap [i386] mview [amd64] @@ -2948,8 +2950,6 @@ Missing dependencies (3781): python-celementtree [i386] python-clips [amd64] python-clips [i386] - python-ctypes (>= 1.0.1) [amd64] - python-ctypes (>= 1.0.1) [i386] python-dev-all (>= 2.6) [amd64] python-dev-all (>= 2.6) [i386] python-elemental [amd64] @@ -3498,6 +3498,8 @@ Missing dependencies (3781): tc-utils [i386] tempo [amd64] tempo [i386] + tetex-bin [amd64] + tetex-bin [i386] tetra [amd64] tetra [i386] texinfo-doc-nonfree [amd64] @@ -3578,6 +3580,8 @@ Missing dependencies (3781): ttf-larabie-straight [i386] ttf-mscorefonts-installer [amd64] ttf-mscorefonts-installer [i386] + ttf-thryomanes [amd64] + ttf-thryomanes [i386] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [amd64] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [i386] ttf2pt1 [amd64] @@ -3746,6 +3750,8 @@ Missing dependencies (3781): xnat [i386] xorsa [amd64] xorsa [i386] + xpdf-reader [amd64] + xpdf-reader [i386] xsidplay [amd64] xsidplay [i386] xtranslate [amd64] diff --git a/system/t05_snapshot/VerifySnapshot7Test_gold b/system/t05_snapshot/VerifySnapshot7Test_gold index b5df842d..8d2420c7 100644 --- a/system/t05_snapshot/VerifySnapshot7Test_gold +++ b/system/t05_snapshot/VerifySnapshot7Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (677): +Missing dependencies (635): abrowser [amd64] abrowser [i386] apache [amd64] @@ -96,8 +96,6 @@ Missing dependencies (677): firefox [i386] freebsd-net-tools [amd64] freebsd-net-tools [i386] - fvwm-gnome (>= 1:2.5.13) [amd64] - fvwm-gnome (>= 1:2.5.13) [i386] gcc-mingw-w64 (<< 4.6.3-3+4) [amd64] gcc-mingw-w64 (<< 4.6.3-3+4) [i386] gdm [amd64] @@ -118,8 +116,6 @@ Missing dependencies (677): gnus [i386] graphicsmagick-im-compat [amd64] graphicsmagick-im-compat [i386] - grep-dctrl (>= 0.11) [amd64] - grep-dctrl (>= 0.11) [i386] gstreamer0.10-plugins-good (<= 0.10.28-2) [amd64] gstreamer0.10-plugins-good (<= 0.10.28-2) [i386] gstreamer0.10-videomixer2 [amd64] @@ -168,8 +164,6 @@ Missing dependencies (677): isdnutils [i386] java-6-runtime [amd64] java-6-runtime [i386] - java-gcj-compat (>= 1.0.77-4) [amd64] - java-gcj-compat (>= 1.0.77-4) [i386] java7-sdk [amd64] java7-sdk [i386] joystick (<< 20051019-6) [amd64] @@ -298,18 +292,8 @@ Missing dependencies (677): libdc1394-dev [i386] libdvdplay0 [amd64] libdvdplay0 [i386] - libemail-mime-creator-perl (>= 1.453) [amd64] - libemail-mime-creator-perl (>= 1.453) [i386] - libemail-mime-modifier-perl (>= 1.441) [amd64] - libemail-mime-modifier-perl (>= 1.441) [i386] libesd-alsa0 (>= 0.2.35) [amd64] libesd-alsa0 (>= 0.2.35) [i386] - libextutils-install-perl (>= 1.52) [amd64] - libextutils-install-perl (>= 1.52) [i386] - libfile-path-perl (>= 2.06) [amd64] - libfile-path-perl (>= 2.06) [i386] - libfile-temp-perl (>= 0.19) [amd64] - libfile-temp-perl (>= 0.19) [i386] libgd-noxpm-perl [amd64] libgd-noxpm-perl [i386] libgd2-dev [amd64] @@ -324,34 +308,18 @@ Missing dependencies (677): libhaml-ruby (<< 3.1) [i386] libicu36-dev [amd64] libicu36-dev [i386] - libio-compress-bzip2-perl (>= 2.008) [amd64] - libio-compress-bzip2-perl (>= 2.008) [i386] - libio-compress-zlib-perl (>= 2.011) [amd64] - libio-compress-zlib-perl (>= 2.011) [i386] - libio-zlib-perl (>= 1.04) [amd64] - libio-zlib-perl (>= 1.04) [i386] libjasper-1.701-dev [amd64] libjasper-1.701-dev [i386] libjibx-java (<< 1.2) [amd64] libjibx-java (<< 1.2) [i386] - liblocale-maketext-simple-perl (>= 0.19) [amd64] - liblocale-maketext-simple-perl (>= 0.19) [i386] libmaildir4 (= 4:4.4.11.1+l10n-3+b1) [amd64] libmaildir4 (= 4:4.4.11.1+l10n-3+b1) [i386] - libmime-perl (>= 5.108) [amd64] - libmime-perl (>= 5.108) [i386] libmodule-metatadata-perl [amd64] libmodule-metatadata-perl [i386] - libmodule-pluggable-perl (>= 3.9) [amd64] - libmodule-pluggable-perl (>= 3.9) [i386] libmotif-dev [amd64] libmotif-dev [i386] - libnet-perl (>= 1.12) [amd64] - libnet-perl (>= 1.12) [i386] libparent (>= 0.223) [amd64] libparent (>= 0.223) [i386] - libparent-perl (>= 0.221) [amd64] - libparent-perl (>= 0.221) [i386] libpng2-dev [amd64] libpng2-dev [i386] libreadline5-dev [amd64] @@ -460,14 +428,10 @@ Missing dependencies (677): postgresql-client-8.3 [i386] puredata (<< 0.43) [amd64] puredata (<< 0.43) [i386] - pykickstart (>= 0.96) [amd64] - pykickstart (>= 0.96) [i386] python-celementtree [amd64] python-celementtree [i386] python-codespeak-lib (<< 1.0) [amd64] python-codespeak-lib (<< 1.0) [i386] - python-ctypes (>= 1.0.0) [amd64] - python-ctypes (>= 1.0.0) [i386] python-elementtree (>= 1.2) [amd64] python-elementtree (>= 1.2) [i386] python-elementtree [amd64] @@ -494,8 +458,6 @@ Missing dependencies (677): python-psycopg (<< 1.1.21) [i386] python-psycopg (>= 1.1.21-14) [amd64] python-psycopg (>= 1.1.21-14) [i386] - python-pynast (>= 1.1) [amd64] - python-pynast (>= 1.1) [i386] python-qt4 (<< 4.7.5) [amd64] python-qt4 (<< 4.7.5) [i386] python-sqlalchemy (<< 0.6.3-2) [amd64] @@ -562,8 +524,6 @@ Missing dependencies (677): urxvt [i386] vidcontrol [amd64] vidcontrol [i386] - w3m-el (>= 1.4.5) [amd64] - w3m-el (>= 1.4.5) [i386] w3m-ssl (>= 0.3) [amd64] w3m-ssl (>= 0.3) [i386] w3mmee (>= 0.3) [amd64] @@ -589,8 +549,6 @@ Missing dependencies (677): wine1.5 [amd64] wine1.5 [i386] wine64-bin (>= 1.4.1-4) [i386] - wish (>= 8.4) [amd64] - wish (>= 8.4) [i386] x-www-browser [amd64] x-www-browser [i386] xcontrib [i386] diff --git a/system/t05_snapshot/VerifySnapshot8Test_gold b/system/t05_snapshot/VerifySnapshot8Test_gold index 16c7c3a4..c00fe579 100644 --- a/system/t05_snapshot/VerifySnapshot8Test_gold +++ b/system/t05_snapshot/VerifySnapshot8Test_gold @@ -1,10 +1,9 @@ Loading packages... Verifying... -Missing dependencies (32): +Missing dependencies (31): dpkg (>= 1.15.4) [amd64] dpkg (>= 1.15.4) [i386] gnuplot (= 4.6.1-1~maverick2) [source] - gnuplot (= 4.6.1-1~maverick2) [source] install-info [amd64] install-info [i386] libc6 (>= 2.11) [amd64] diff --git a/system/t06_publish/AzurePublish1Test_binary b/system/t06_publish/AzurePublish1Test_binary new file mode 100644 index 00000000..1fd182e4 --- /dev/null +++ b/system/t06_publish/AzurePublish1Test_binary @@ -0,0 +1,27 @@ + + + (name, value) pairs from the user, via conventional methods such as + . + . + Boost version (currently 1.49). + Library to let program developers obtain program options, that is + This package forms part of the Boost C++ Libraries collection. + This package is a dependency package, which depends on Debian's default + command line and config file. +Architecture: i386 +Depends: libboost-program-options1.49-dev +Description: program options library for C++ (default version) +Filename: pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb +Homepage: http://www.boost.org/libs/program_options/ +Installed-Size: 26 +MD5sum: 0035d7822b2f8f0ec4013f270fd650c2 +Maintainer: Debian Boost Team +Package: libboost-program-options-dev +Priority: optional +SHA1: 36895eb64cfe89c33c0a2f7ac2f0c6e0e889e04b +SHA256: c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12 +SHA512: d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c +Section: libdevel +Size: 2738 +Source: boost-defaults +Version: 1.49.0.1 \ No newline at end of file diff --git a/system/t06_publish/AzurePublish1Test_gold b/system/t06_publish/AzurePublish1Test_gold new file mode 100644 index 00000000..b65701eb --- /dev/null +++ b/system/t06_publish/AzurePublish1Test_gold @@ -0,0 +1,13 @@ +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 repo local-repo has been successfully published. +Now you can add following line to apt sources: + deb http://your-server/ maverick main + deb-src http://your-server/ maverick 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. diff --git a/system/t06_publish/AzurePublish1Test_release b/system/t06_publish/AzurePublish1Test_release new file mode 100644 index 00000000..78154c5b --- /dev/null +++ b/system/t06_publish/AzurePublish1Test_release @@ -0,0 +1,11 @@ +Origin: . maverick +Label: . maverick +Suite: maverick +Codename: maverick +Architectures: i386 +Components: main +Description: Generated by aptly +MD5Sum: +SHA1: +SHA256: +SHA512: diff --git a/system/t06_publish/AzurePublish1Test_sources b/system/t06_publish/AzurePublish1Test_sources new file mode 100644 index 00000000..f561df83 --- /dev/null +++ b/system/t06_publish/AzurePublish1Test_sources @@ -0,0 +1,57 @@ + + + + 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz + 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz + 262cac59a2e81c7f110851ff9670c97ffc3d192d9937b880422a0907f26340d43e7de7e68b904a4fb10bedb02b65c3bd1f7bdd20ea8c4293e690e7a8e0e70ee5 893 pyspi-0.6.1-1.3.stripped.dsc + 289d3aefa970876e9c43686ce2b02f478d7f3ed35a713928464a98d54ae4fca3 893 pyspi-0.6.1-1.3.stripped.dsc + 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz + 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz + 2f5bd47cf38852b6fc927a50f98c1448 893 pyspi-0.6.1-1.3.stripped.dsc + 384b5e94b4113262e41bda1a2563f4f439cb8c97f43e2caefe16d7626718c21b36d3145b915eed24053eaa7fe3b6186494a87a3fcf9627f6e653b54bb3caa897 3456 pyspi_0.6.1-1.3.diff.gz + 384b5e94b4113262e41bda1a2563f4f439cb8c97f43e2caefe16d7626718c21b36d3145b915eed24053eaa7fe3b6186494a87a3fcf9627f6e653b54bb3caa897 3456 pyspi_0.6.1-1.3.diff.gz + 5005fbd1f30637edc1d380b30f45db9b79100d07 893 pyspi-0.6.1-1.3.stripped.dsc + 56c8a9b1f4ab636052be8966690998cbe865cd6c 1782 pyspi_0.6.1-1.3.dsc + 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz + 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz + 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz + 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz + 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz + 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz + b72cb94699298a117b7c82641c68b6fd 1782 pyspi_0.6.1-1.3.dsc + c278f52953203292bcc828bcf05aee456b160f91716f51ec1a1dbbcdb8b08fc29183d0a1135629fc0ebe86a3e84cedc685c3aa1714b70cc5db8877d40e754d7f 29063 pyspi_0.6.1.orig.tar.gz + c278f52953203292bcc828bcf05aee456b160f91716f51ec1a1dbbcdb8b08fc29183d0a1135629fc0ebe86a3e84cedc685c3aa1714b70cc5db8877d40e754d7f 29063 pyspi_0.6.1.orig.tar.gz + d494aaf526f1ec6b02f14c2f81e060a5722d6532ddc760ec16972e45c2625989 1782 pyspi_0.6.1-1.3.dsc + def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz + def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz + fde06b7dc5762a04986d0669420822f6a1e82b195322ae9cbd2dae40bda557c57ad77fe3546007ea645f801c4cd30ef4eb0e96efb2dee6b71c4c9a187d643683 1782 pyspi_0.6.1-1.3.dsc +Architecture: any +Architecture: any +Binary: python-at-spi +Binary: python-at-spi +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Checksums-Sha1: +Checksums-Sha1: +Checksums-Sha256: +Checksums-Sha256: +Checksums-Sha512: +Checksums-Sha512: +Directory: pool/main/p/pyspi +Directory: pool/main/p/pyspi +Files: +Files: +Format: 1.0 +Format: 1.0 +Homepage: http://people.redhat.com/zcerza/dogtail +Homepage: http://people.redhat.com/zcerza/dogtail +Maintainer: Jose Carlos Garcia Sogo +Maintainer: Jose Carlos Garcia Sogo +Package: pyspi +Package: pyspi +Standards-Version: 3.7.3 +Standards-Version: 3.7.3 +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Version: 0.6.1-1.3 +Version: 0.6.1-1.4 \ No newline at end of file diff --git a/system/t06_publish/AzurePublish2Test_binary b/system/t06_publish/AzurePublish2Test_binary new file mode 100644 index 00000000..1fd182e4 --- /dev/null +++ b/system/t06_publish/AzurePublish2Test_binary @@ -0,0 +1,27 @@ + + + (name, value) pairs from the user, via conventional methods such as + . + . + Boost version (currently 1.49). + Library to let program developers obtain program options, that is + This package forms part of the Boost C++ Libraries collection. + This package is a dependency package, which depends on Debian's default + command line and config file. +Architecture: i386 +Depends: libboost-program-options1.49-dev +Description: program options library for C++ (default version) +Filename: pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb +Homepage: http://www.boost.org/libs/program_options/ +Installed-Size: 26 +MD5sum: 0035d7822b2f8f0ec4013f270fd650c2 +Maintainer: Debian Boost Team +Package: libboost-program-options-dev +Priority: optional +SHA1: 36895eb64cfe89c33c0a2f7ac2f0c6e0e889e04b +SHA256: c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12 +SHA512: d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c +Section: libdevel +Size: 2738 +Source: boost-defaults +Version: 1.49.0.1 \ No newline at end of file diff --git a/system/t06_publish/AzurePublish2Test_gold b/system/t06_publish/AzurePublish2Test_gold new file mode 100644 index 00000000..0ac6b345 --- /dev/null +++ b/system/t06_publish/AzurePublish2Test_gold @@ -0,0 +1,9 @@ +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: +Cleaning up published repository azure:test1:./maverick... +Cleaning up component 'main'... + +Published local repository azure:test1:./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/AzurePublish2Test_release b/system/t06_publish/AzurePublish2Test_release new file mode 100644 index 00000000..78154c5b --- /dev/null +++ b/system/t06_publish/AzurePublish2Test_release @@ -0,0 +1,11 @@ +Origin: . maverick +Label: . maverick +Suite: maverick +Codename: maverick +Architectures: i386 +Components: main +Description: Generated by aptly +MD5Sum: +SHA1: +SHA256: +SHA512: diff --git a/system/t06_publish/AzurePublish2Test_sources b/system/t06_publish/AzurePublish2Test_sources new file mode 100644 index 00000000..e69de29b diff --git a/system/t06_publish/AzurePublish3Test_binary b/system/t06_publish/AzurePublish3Test_binary new file mode 100644 index 00000000..aea5c35d --- /dev/null +++ b/system/t06_publish/AzurePublish3Test_binary @@ -0,0 +1,30 @@ + + + . + . + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + Data files and self-defined functions can be manipulated by the internal + Gnuplot is a portable command-line driven interactive data and function + This package contains the terminal driver that enables gnuplot to plot + and can work with complex numbers. + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + gnuplot. + images interactively under X11. Most users will want this, it is however + is packaged in gnuplot-x11. + packaged separately so that low-end systems don't need X installed to use + plotting utility that supports lots of output formats, including drivers +Architecture: i386 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Description: Command-line driven interactive plotting program +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb +Installed-Size: 1604 +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +Maintainer: Debian Science Team +Package: gnuplot-x11 +Priority: optional +Replaces: gnuplot (<< 4.0.0) +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 +Section: math +Size: 724388 +Source: gnuplot +Version: 4.6.1-1~maverick2 \ No newline at end of file diff --git a/system/t06_publish/AzurePublish3Test_gold b/system/t06_publish/AzurePublish3Test_gold new file mode 100644 index 00000000..a346b469 --- /dev/null +++ b/system/t06_publish/AzurePublish3Test_gold @@ -0,0 +1,9 @@ +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: +Cleaning up published repository azure:test1:./maverick... +Cleaning up component 'main'... + +Published snapshot repository azure:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/AzurePublish3Test_release b/system/t06_publish/AzurePublish3Test_release new file mode 100644 index 00000000..91ff3759 --- /dev/null +++ b/system/t06_publish/AzurePublish3Test_release @@ -0,0 +1,11 @@ +Origin: LP-PPA-gladky-anton-gnuplot +Label: . maverick +Suite: maverick +Codename: maverick +Architectures: amd64 i386 +Components: main +Description: Generated by aptly +MD5Sum: +SHA1: +SHA256: +SHA512: diff --git a/system/t06_publish/AzurePublish4Test_gold b/system/t06_publish/AzurePublish4Test_gold new file mode 100644 index 00000000..b5d583c1 --- /dev/null +++ b/system/t06_publish/AzurePublish4Test_gold @@ -0,0 +1,4 @@ +Published repositories: + * azure:test1:./maverick [amd64, i386] publishes {main: [local-repo]} + * azure:test1:./xyz [amd64, i386] publishes {main: [local-repo]} + * azure:test1:prefix/maverick [amd64, i386] publishes {main: [local-repo]} diff --git a/system/t06_publish/AzurePublish5Test_gold b/system/t06_publish/AzurePublish5Test_gold new file mode 100644 index 00000000..bef652cb --- /dev/null +++ b/system/t06_publish/AzurePublish5Test_gold @@ -0,0 +1,4 @@ +Cleaning up published repository azure:test1:./sq2... +Cleaning up component 'main'... + +Published repository has been removed successfully. diff --git a/system/t06_publish/PublishDrop3Test_gold b/system/t06_publish/PublishDrop3Test_gold index eb8ce9c0..1da15e9a 100644 --- a/system/t06_publish/PublishDrop3Test_gold +++ b/system/t06_publish/PublishDrop3Test_gold @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/sq1... -Cleaning up prefix "." components main... +Cleaning up published repository ./sq1... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishDrop5Test_gold b/system/t06_publish/PublishDrop5Test_gold index 79be1a16..2313ec65 100644 --- a/system/t06_publish/PublishDrop5Test_gold +++ b/system/t06_publish/PublishDrop5Test_gold @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/sq2... -Cleaning up prefix "." components main... +Cleaning up published repository ./sq2... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishDrop9Test_gold b/system/t06_publish/PublishDrop9Test_gold index eb8ce9c0..1da15e9a 100644 --- a/system/t06_publish/PublishDrop9Test_gold +++ b/system/t06_publish/PublishDrop9Test_gold @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/sq1... -Cleaning up prefix "." components main... +Cleaning up published repository ./sq1... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishList5Test_gold b/system/t06_publish/PublishList5Test_gold index 61f468b9..58b5dc05 100644 --- a/system/t06_publish/PublishList5Test_gold +++ b/system/t06_publish/PublishList5Test_gold @@ -6,8 +6,10 @@ "i386" ], "ButAutomaticUpgrades": "", + "Codename": "", "Distribution": "maverick", "Label": "", + "MultiDist": false, "NotAutomatic": "", "Origin": "LP-PPA-gladky-anton-gnuplot", "Path": "./maverick", @@ -29,8 +31,10 @@ "amd64" ], "ButAutomaticUpgrades": "", + "Codename": "", "Distribution": "wheezy", "Label": "", + "MultiDist": false, "NotAutomatic": "", "Origin": "", "Path": "ppa/smira/wheezy", @@ -53,8 +57,10 @@ "i386" ], "ButAutomaticUpgrades": "", + "Codename": "", "Distribution": "maverick", "Label": "", + "MultiDist": false, "NotAutomatic": "", "Origin": "origin1", "Path": "ppa/tr1/maverick", @@ -77,8 +83,10 @@ "i386" ], "ButAutomaticUpgrades": "", + "Codename": "", "Distribution": "maverick", "Label": "label1", + "MultiDist": false, "NotAutomatic": "", "Origin": "", "Path": "ppa/tr2/maverick", diff --git a/system/t06_publish/PublishRepo34Test_gold b/system/t06_publish/PublishRepo34Test_gold new file mode 100644 index 00000000..365295fa --- /dev/null +++ b/system/t06_publish/PublishRepo34Test_gold @@ -0,0 +1,14 @@ +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 repo local-repo has 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 main + deb-src http://your-server/ maverick 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. diff --git a/system/t06_publish/PublishShow3Test_gold b/system/t06_publish/PublishShow3Test_gold index b68fbef1..aefbff7c 100644 --- a/system/t06_publish/PublishShow3Test_gold +++ b/system/t06_publish/PublishShow3Test_gold @@ -5,8 +5,10 @@ "i386" ], "ButAutomaticUpgrades": "", + "Codename": "", "Distribution": "maverick", "Label": "", + "MultiDist": false, "NotAutomatic": "", "Origin": "LP-PPA-gladky-anton-gnuplot", "Path": "./maverick", diff --git a/system/t06_publish/PublishShow4Test_gold b/system/t06_publish/PublishShow4Test_gold index 24fe5949..cf95faee 100644 --- a/system/t06_publish/PublishShow4Test_gold +++ b/system/t06_publish/PublishShow4Test_gold @@ -5,8 +5,10 @@ "i386" ], "ButAutomaticUpgrades": "", + "Codename": "", "Distribution": "maverick", "Label": "", + "MultiDist": false, "NotAutomatic": "", "Origin": "LP-PPA-gladky-anton-gnuplot", "Path": "ppa/smira/maverick", diff --git a/system/t06_publish/PublishShow5Test_gold b/system/t06_publish/PublishShow5Test_gold new file mode 100644 index 00000000..7e6f4a2c --- /dev/null +++ b/system/t06_publish/PublishShow5Test_gold @@ -0,0 +1,5 @@ +Prefix: . +Distribution: wheezy +Architectures: i386 +Sources: + main: local-repo [local] diff --git a/system/t06_publish/PublishSnapshot1Test_release_amd64 b/system/t06_publish/PublishSnapshot1Test_release_amd64 index b69b5727..08216879 100644 --- a/system/t06_publish/PublishSnapshot1Test_release_amd64 +++ b/system/t06_publish/PublishSnapshot1Test_release_amd64 @@ -2,5 +2,6 @@ Origin: LP-PPA-gladky-anton-gnuplot Label: . maverick Archive: maverick Suite: maverick +Codename: maverick Architecture: amd64 Component: main diff --git a/system/t06_publish/PublishSnapshot1Test_release_i386 b/system/t06_publish/PublishSnapshot1Test_release_i386 index 484aabc6..c776b4bc 100644 --- a/system/t06_publish/PublishSnapshot1Test_release_i386 +++ b/system/t06_publish/PublishSnapshot1Test_release_i386 @@ -2,5 +2,6 @@ Origin: LP-PPA-gladky-anton-gnuplot Label: . maverick Archive: maverick Suite: maverick +Codename: maverick Architecture: i386 Component: main diff --git a/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 b/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 index e93dcfe6..d50da52d 100644 --- a/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 +++ b/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 @@ -2,5 +2,6 @@ Origin: Debian Label: . stretch Archive: stretch Suite: stretch +Codename: stretch Architecture: i386 Component: main diff --git a/system/t06_publish/PublishSnapshot39Test_release_amd64 b/system/t06_publish/PublishSnapshot39Test_release_amd64 index cfc9b675..669008de 100644 --- a/system/t06_publish/PublishSnapshot39Test_release_amd64 +++ b/system/t06_publish/PublishSnapshot39Test_release_amd64 @@ -2,5 +2,6 @@ Origin: LP-PPA-gladky-anton-gnuplot Label: . maverick Archive: maverick Suite: stable +Codename: maverick Architecture: amd64 Component: main diff --git a/system/t06_publish/PublishSnapshot39Test_release_i386 b/system/t06_publish/PublishSnapshot39Test_release_i386 index a509da19..17bccc9f 100644 --- a/system/t06_publish/PublishSnapshot39Test_release_i386 +++ b/system/t06_publish/PublishSnapshot39Test_release_i386 @@ -2,5 +2,6 @@ Origin: LP-PPA-gladky-anton-gnuplot Label: . maverick Archive: maverick Suite: stable +Codename: maverick Architecture: i386 Component: main diff --git a/system/t06_publish/PublishSnapshot41Test_gold b/system/t06_publish/PublishSnapshot41Test_gold new file mode 100644 index 00000000..06f16c33 --- /dev/null +++ b/system/t06_publish/PublishSnapshot41Test_gold @@ -0,0 +1,14 @@ +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: + +Snapshot snap41 has 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/ buster/updates main + deb-src http://your-server/ buster/updates 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. diff --git a/system/t06_publish/PublishSnapshot42Test_gold b/system/t06_publish/PublishSnapshot42Test_gold new file mode 100644 index 00000000..8cf10f19 --- /dev/null +++ b/system/t06_publish/PublishSnapshot42Test_gold @@ -0,0 +1,13 @@ +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: + +Snapshot snap1 has 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 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. diff --git a/system/t06_publish/PublishSourceAdd1Test_gold b/system/t06_publish/PublishSourceAdd1Test_gold new file mode 100644 index 00000000..12c4aa07 --- /dev/null +++ b/system/t06_publish/PublishSourceAdd1Test_gold @@ -0,0 +1,3 @@ +Adding component 'test' with source 'snap2' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceAdd2Test_gold b/system/t06_publish/PublishSourceAdd2Test_gold new file mode 100644 index 00000000..fcdf6d02 --- /dev/null +++ b/system/t06_publish/PublishSourceAdd2Test_gold @@ -0,0 +1,4 @@ +Adding component 'test' with source 'snap2' [snapshot]... +Adding component 'other-test' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceAdd3Test_gold b/system/t06_publish/PublishSourceAdd3Test_gold new file mode 100644 index 00000000..dd31661c --- /dev/null +++ b/system/t06_publish/PublishSourceAdd3Test_gold @@ -0,0 +1 @@ +ERROR: unable to add: component 'main' has already been added diff --git a/system/t06_publish/PublishSourceDrop1Test_gold b/system/t06_publish/PublishSourceDrop1Test_gold new file mode 100644 index 00000000..5f05ba78 --- /dev/null +++ b/system/t06_publish/PublishSourceDrop1Test_gold @@ -0,0 +1 @@ +Source changes have been removed successfully. diff --git a/system/t06_publish/PublishSourceList1Test_gold b/system/t06_publish/PublishSourceList1Test_gold new file mode 100644 index 00000000..364ef5c2 --- /dev/null +++ b/system/t06_publish/PublishSourceList1Test_gold @@ -0,0 +1,3 @@ +Sources: + main: snap1 [snapshot] + test: snap2 [snapshot] diff --git a/system/t06_publish/PublishSourceList2Test_gold b/system/t06_publish/PublishSourceList2Test_gold new file mode 100644 index 00000000..ba30e4af --- /dev/null +++ b/system/t06_publish/PublishSourceList2Test_gold @@ -0,0 +1,10 @@ +[ + { + "Component": "main", + "Name": "snap1" + }, + { + "Component": "test", + "Name": "snap2" + } +] diff --git a/system/t06_publish/PublishSourceList3Test_gold b/system/t06_publish/PublishSourceList3Test_gold new file mode 100644 index 00000000..03e2a99a --- /dev/null +++ b/system/t06_publish/PublishSourceList3Test_gold @@ -0,0 +1 @@ +ERROR: unable to list: no source changes exist diff --git a/system/t06_publish/PublishSourceRemove1Test_gold b/system/t06_publish/PublishSourceRemove1Test_gold new file mode 100644 index 00000000..40fcf4de --- /dev/null +++ b/system/t06_publish/PublishSourceRemove1Test_gold @@ -0,0 +1,3 @@ +Removing component 'test' with source 'snap2' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceRemove2Test_gold b/system/t06_publish/PublishSourceRemove2Test_gold new file mode 100644 index 00000000..f8270658 --- /dev/null +++ b/system/t06_publish/PublishSourceRemove2Test_gold @@ -0,0 +1,4 @@ +Removing component 'test' with source 'snap2' [snapshot]... +Removing component 'other-test' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceRemove3Test_gold b/system/t06_publish/PublishSourceRemove3Test_gold new file mode 100644 index 00000000..d4ebcbb1 --- /dev/null +++ b/system/t06_publish/PublishSourceRemove3Test_gold @@ -0,0 +1 @@ +ERROR: unable to remove: component 'not-existent' does not exist diff --git a/system/t06_publish/PublishSourceReplace1Test_gold b/system/t06_publish/PublishSourceReplace1Test_gold new file mode 100644 index 00000000..c3b03ff9 --- /dev/null +++ b/system/t06_publish/PublishSourceReplace1Test_gold @@ -0,0 +1,5 @@ +Replacing source list... +Adding component 'main-new' with source 'snap2' [snapshot]... +Adding component 'test-new' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceUpdate1Test_gold b/system/t06_publish/PublishSourceUpdate1Test_gold new file mode 100644 index 00000000..2f218bf2 --- /dev/null +++ b/system/t06_publish/PublishSourceUpdate1Test_gold @@ -0,0 +1,3 @@ +Updating component 'main' with source 'snap2' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceUpdate2Test_gold b/system/t06_publish/PublishSourceUpdate2Test_gold new file mode 100644 index 00000000..88178e48 --- /dev/null +++ b/system/t06_publish/PublishSourceUpdate2Test_gold @@ -0,0 +1,4 @@ +Updating component 'main' with source 'snap2' [snapshot]... +Updating component 'test' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceUpdate3Test_gold b/system/t06_publish/PublishSourceUpdate3Test_gold new file mode 100644 index 00000000..c3e6eb88 --- /dev/null +++ b/system/t06_publish/PublishSourceUpdate3Test_gold @@ -0,0 +1 @@ +ERROR: unable to update: component 'not-existent' does not exist diff --git a/system/t06_publish/PublishSwitch11Test_gold b/system/t06_publish/PublishSwitch11Test_gold index 2385a0ed..5f37e805 100644 --- a/system/t06_publish/PublishSwitch11Test_gold +++ b/system/t06_publish/PublishSwitch11Test_gold @@ -5,6 +5,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for snapshot ./maverick [i386, source] publishes {main: [snap2]: Snapshot from local repo [local-repo2]} has been successfully switched to new snapshot. +Published snapshot repository ./maverick [i386, source] publishes {main: [snap2]: Snapshot from local repo [local-repo2]} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch12Test_gold b/system/t06_publish/PublishSwitch12Test_gold index 2fb3bcac..ce8636a4 100644 --- a/system/t06_publish/PublishSwitch12Test_gold +++ b/system/t06_publish/PublishSwitch12Test_gold @@ -1 +1 @@ -ERROR: unable to switch: component c is not in published repository +ERROR: unable to switch: component c does not exist in published repository diff --git a/system/t06_publish/PublishSwitch13Test_gold b/system/t06_publish/PublishSwitch13Test_gold index 3f440f06..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch13Test_gold +++ b/system/t06_publish/PublishSwitch13Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch14Test_gold b/system/t06_publish/PublishSwitch14Test_gold index 9b8ce8f1..1cdaf085 100644 --- a/system/t06_publish/PublishSwitch14Test_gold +++ b/system/t06_publish/PublishSwitch14Test_gold @@ -4,4 +4,4 @@ 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: -Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch15Test_gold b/system/t06_publish/PublishSwitch15Test_gold index 3f440f06..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch15Test_gold +++ b/system/t06_publish/PublishSwitch15Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch16Test_gold b/system/t06_publish/PublishSwitch16Test_gold new file mode 100644 index 00000000..6794c13d --- /dev/null +++ b/system/t06_publish/PublishSwitch16Test_gold @@ -0,0 +1,9 @@ +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: +Cleaning up published repository ./bookworm... +Cleaning up component 'main'... + +Published snapshot repository ./bookworm (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch1Test_gold b/system/t06_publish/PublishSwitch1Test_gold index 3f440f06..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch1Test_gold +++ b/system/t06_publish/PublishSwitch1Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch2Test_gold b/system/t06_publish/PublishSwitch2Test_gold index c8464423..7cfc4f75 100644 --- a/system/t06_publish/PublishSwitch2Test_gold +++ b/system/t06_publish/PublishSwitch2Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "ppa" components main... +Cleaning up published repository ppa/maverick... +Cleaning up component 'main'... -Publish for snapshot ppa/maverick [amd64, i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new snapshot. +Published snapshot repository ppa/maverick [amd64, i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch3Test_gold b/system/t06_publish/PublishSwitch3Test_gold index 3f440f06..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch3Test_gold +++ b/system/t06_publish/PublishSwitch3Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch4Test_gold b/system/t06_publish/PublishSwitch4Test_gold index 7256431a..74451164 100644 --- a/system/t06_publish/PublishSwitch4Test_gold +++ b/system/t06_publish/PublishSwitch4Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "ppa" components main... +Cleaning up published repository ppa/maverick... +Cleaning up component 'main'... -Publish for snapshot ppa/maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new snapshot. +Published snapshot repository ppa/maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new source. diff --git a/system/t06_publish/PublishSwitch5Test_gold b/system/t06_publish/PublishSwitch5Test_gold index c28babdc..498bf248 100644 --- a/system/t06_publish/PublishSwitch5Test_gold +++ b/system/t06_publish/PublishSwitch5Test_gold @@ -1 +1 @@ -ERROR: unable to update: published repo with storage:prefix/distribution ppa/maverick not found +ERROR: unable to switch: published repo with storage:prefix/distribution ppa/maverick not found diff --git a/system/t06_publish/PublishSwitch6Test_gold b/system/t06_publish/PublishSwitch6Test_gold index bb45822f..27a20838 100644 --- a/system/t06_publish/PublishSwitch6Test_gold +++ b/system/t06_publish/PublishSwitch6Test_gold @@ -1 +1 @@ -ERROR: unable to update: not a snapshot publish +ERROR: unable to switch: not a published snapshot repository diff --git a/system/t06_publish/PublishSwitch8Test_gold b/system/t06_publish/PublishSwitch8Test_gold index d9870ff9..44d1fdbe 100644 --- a/system/t06_publish/PublishSwitch8Test_gold +++ b/system/t06_publish/PublishSwitch8Test_gold @@ -3,6 +3,8 @@ 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: -Cleaning up prefix "." components b, c... +Cleaning up published repository ./maverick... +Cleaning up component 'b'... +Cleaning up component 'c'... -Publish for snapshot ./maverick [amd64, i386, source] publishes {a: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {b: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'}, {c: [local2]: Snapshot from local repo [local-repo]} has been successfully switched to new snapshot. +Published snapshot repository ./maverick [amd64, i386, source] publishes {a: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {b: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'}, {c: [local2]: Snapshot from local repo [local-repo]} has been successfully switched to new source. diff --git a/system/t06_publish/PublishUpdate10Test_gold b/system/t06_publish/PublishUpdate10Test_gold index bda2f4dd..010d5ec4 100644 --- a/system/t06_publish/PublishUpdate10Test_gold +++ b/system/t06_publish/PublishUpdate10Test_gold @@ -5,6 +5,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate11Test_gold b/system/t06_publish/PublishUpdate11Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate11Test_gold +++ b/system/t06_publish/PublishUpdate11Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate12Test_gold b/system/t06_publish/PublishUpdate12Test_gold index 3b7a5709..d3a202df 100644 --- a/system/t06_publish/PublishUpdate12Test_gold +++ b/system/t06_publish/PublishUpdate12Test_gold @@ -4,4 +4,4 @@ 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: -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate13Test_gold b/system/t06_publish/PublishUpdate13Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate13Test_gold +++ b/system/t06_publish/PublishUpdate13Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate14Test_gold b/system/t06_publish/PublishUpdate14Test_gold new file mode 100644 index 00000000..12ddf71c --- /dev/null +++ b/system/t06_publish/PublishUpdate14Test_gold @@ -0,0 +1,9 @@ +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: +Cleaning up published repository ./bookworm... +Cleaning up component 'main'... + +Published local repository ./bookworm [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate15Test_gold b/system/t06_publish/PublishUpdate15Test_gold new file mode 100644 index 00000000..a0624c8e --- /dev/null +++ b/system/t06_publish/PublishUpdate15Test_gold @@ -0,0 +1,8 @@ +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: +Cleaning up published repository ./maverick... + +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {other-test: [snap3]: Created as empty}, {test: [snap2]: Created as empty} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate16Test_gold b/system/t06_publish/PublishUpdate16Test_gold new file mode 100644 index 00000000..927ac538 --- /dev/null +++ b/system/t06_publish/PublishUpdate16Test_gold @@ -0,0 +1,14 @@ +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: +Cleaning up published repository ./maverick... +Removing component 'other-test'... +Removing ${HOME}/.aptly/public/dists/maverick/other-test... +Removing component 'test'... +Removing ${HOME}/.aptly/public/dists/maverick/test... +Removing ${HOME}/.aptly/public/pool/other-test... +Removing ${HOME}/.aptly/public/pool/test... + +Published snapshot repository ./maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate17Test_gold b/system/t06_publish/PublishUpdate17Test_gold new file mode 100644 index 00000000..69c0182f --- /dev/null +++ b/system/t06_publish/PublishUpdate17Test_gold @@ -0,0 +1,10 @@ +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: +Cleaning up published repository ./maverick... +Cleaning up component 'other-test'... +Cleaning up component 'test'... + +Published snapshot repository ./maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {other-test: [snap5]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {test: [snap4]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate18Test_gold b/system/t06_publish/PublishUpdate18Test_gold new file mode 100644 index 00000000..ca805720 --- /dev/null +++ b/system/t06_publish/PublishUpdate18Test_gold @@ -0,0 +1,12 @@ +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: +Cleaning up published repository ./maverick... +Removing component 'main'... +Removing ${HOME}/.aptly/public/dists/maverick/main... +Removing ${HOME}/.aptly/public/pool/main... +Cleaning up component 'test'... + +Published snapshot repository ./maverick [i386] publishes {other-test: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {test: [snap3]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate1Test_gold b/system/t06_publish/PublishUpdate1Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate1Test_gold +++ b/system/t06_publish/PublishUpdate1Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate2Test_gold b/system/t06_publish/PublishUpdate2Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate2Test_gold +++ b/system/t06_publish/PublishUpdate2Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate3Test_gold b/system/t06_publish/PublishUpdate3Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate3Test_gold +++ b/system/t06_publish/PublishUpdate3Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate4Test_gold b/system/t06_publish/PublishUpdate4Test_gold index dce245d5..c996227a 100644 --- a/system/t06_publish/PublishUpdate4Test_gold +++ b/system/t06_publish/PublishUpdate4Test_gold @@ -3,6 +3,6 @@ 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: -Cleaning up prefix "." components main... +Cleaning up component 'main' in prefix '.'... -Publish for local repo ./maverick [source] publishes {main: [local-repo]} has been successfully updated. +Published local repository ./maverick [source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate7Test_gold b/system/t06_publish/PublishUpdate7Test_gold index 813d7055..b0f13d5c 100644 --- a/system/t06_publish/PublishUpdate7Test_gold +++ b/system/t06_publish/PublishUpdate7Test_gold @@ -3,6 +3,8 @@ 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: -Cleaning up prefix "." components contrib, main... +Cleaning up published repository ./maverick... +Cleaning up component 'contrib'... +Cleaning up component 'main'... -Publish for local repo ./maverick [i386, source] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated. +Published local repository ./maverick [i386, source] publishes {contrib: [repo2]}, {main: [repo1]} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate8Test_gold b/system/t06_publish/PublishUpdate8Test_gold index 09aa9845..b69df45f 100644 --- a/system/t06_publish/PublishUpdate8Test_gold +++ b/system/t06_publish/PublishUpdate8Test_gold @@ -1,6 +1,8 @@ Loading packages... Generating metadata files and linking package files... Finalizing metadata files... -Cleaning up prefix "." components contrib, main... +Cleaning up published repository ./squeeze... +Cleaning up component 'contrib'... +Cleaning up component 'main'... -Publish for local repo ./squeeze [i386] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated. +Published local repository ./squeeze [i386] publishes {contrib: [repo2]}, {main: [repo1]} has been updated successfully. diff --git a/system/t06_publish/S3Publish2Test_gold b/system/t06_publish/S3Publish2Test_gold index 12c9c0e6..cd68115c 100644 --- a/system/t06_publish/S3Publish2Test_gold +++ b/system/t06_publish/S3Publish2Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./maverick... +Cleaning up component 'main'... -Publish for local repo s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/S3Publish3Test_gold b/system/t06_publish/S3Publish3Test_gold index 7d609f0f..348fbe81 100644 --- a/system/t06_publish/S3Publish3Test_gold +++ b/system/t06_publish/S3Publish3Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./maverick... +Cleaning up component 'main'... -Publish for snapshot s3:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository s3:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source. diff --git a/system/t06_publish/S3Publish5Test_gold b/system/t06_publish/S3Publish5Test_gold index cadce02c..0b8f635f 100644 --- a/system/t06_publish/S3Publish5Test_gold +++ b/system/t06_publish/S3Publish5Test_gold @@ -1,3 +1,4 @@ -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./sq2... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/S3Publish6Test_gold b/system/t06_publish/S3Publish6Test_gold index 12c9c0e6..cd68115c 100644 --- a/system/t06_publish/S3Publish6Test_gold +++ b/system/t06_publish/S3Publish6Test_gold @@ -3,6 +3,7 @@ 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: -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./maverick... +Cleaning up component 'main'... -Publish for local repo s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/SwiftPublish2Test_gold b/system/t06_publish/SwiftPublish2Test_gold index 5beaf677..40ce0140 100644 --- a/system/t06_publish/SwiftPublish2Test_gold +++ b/system/t06_publish/SwiftPublish2Test_gold @@ -3,6 +3,6 @@ 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: -Cleaning up prefix "." components main... +Cleaning up prefix '.' components main... -Publish for local repo swift:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository swift:test1:./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/SwiftPublish3Test_gold b/system/t06_publish/SwiftPublish3Test_gold index 16c322a5..d8334a59 100644 --- a/system/t06_publish/SwiftPublish3Test_gold +++ b/system/t06_publish/SwiftPublish3Test_gold @@ -3,6 +3,6 @@ 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: -Cleaning up prefix "." components main... +Cleaning up prefix '.' components main... -Publish for snapshot swift:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. +Published snapshot repository swift:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. diff --git a/system/t06_publish/SwiftPublish5Test_gold b/system/t06_publish/SwiftPublish5Test_gold index cadce02c..b8a740ad 100644 --- a/system/t06_publish/SwiftPublish5Test_gold +++ b/system/t06_publish/SwiftPublish5Test_gold @@ -1,3 +1,3 @@ -Cleaning up prefix "." components main... +Cleaning up prefix '.' components main... Published repository has been removed successfully. diff --git a/system/t06_publish/azure.py b/system/t06_publish/azure.py new file mode 100644 index 00000000..962021e1 --- /dev/null +++ b/system/t06_publish/azure.py @@ -0,0 +1,211 @@ +from azure_lib import AzureTest + + +def strip_processor(output): + return '\n'.join( + [ + l + for l in output.split('\n') + if not l.startswith(' ') and not l.startswith('Date:') + ] + ) + + +class AzurePublish1Test(AzureTest): + """ + publish to Azure: from repo + """ + + fixtureCmds = [ + 'aptly repo create -distribution=maverick local-repo', + 'aptly repo add local-repo ${files}', + 'aptly repo remove local-repo libboost-program-options-dev_1.62.0.1_i386', + ] + runCmd = 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo azure:test1:' + + def check(self): + super(AzurePublish1Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/source/Sources') + self.check_exists('public/dists/maverick/main/source/Sources.gz') + self.check_exists('public/dists/maverick/main/source/Sources.bz2') + + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists( + 'public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb' + ) + + # # verify contents except of sums + self.check_file_contents( + 'public/dists/maverick/Release', 'release', match_prepare=strip_processor + ) + self.check_file_contents( + 'public/dists/maverick/main/source/Sources', + 'sources', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + self.check_file_contents( + 'public/dists/maverick/main/binary-i386/Packages', + 'binary', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + + +class AzurePublish2Test(AzureTest): + """ + publish to Azure: publish update removed some packages + """ + + fixtureCmds = [ + 'aptly repo create -distribution=maverick local-repo', + 'aptly repo add local-repo ${files}/', + 'aptly repo remove local-repo libboost-program-options-dev_1.62.0.1_i386', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo azure:test1:', + 'aptly repo remove local-repo pyspi', + ] + runCmd = 'aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick azure:test1:' + + def check(self): + super(AzurePublish2Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/source/Sources') + self.check_exists('public/dists/maverick/main/source/Sources.gz') + self.check_exists('public/dists/maverick/main/source/Sources.bz2') + + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists( + 'public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb' + ) + + # verify contents except of sums + self.check_file_contents( + 'public/dists/maverick/Release', 'release', match_prepare=strip_processor + ) + self.check_file_contents( + 'public/dists/maverick/main/source/Sources', + 'sources', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + self.check_file_contents( + 'public/dists/maverick/main/binary-i386/Packages', + 'binary', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + + +class AzurePublish3Test(AzureTest): + """ + publish to Azure: publish switch - removed some packages + """ + + fixtureDB = True + fixturePool = True + fixtureCmds = [ + 'aptly snapshot create snap1 from mirror gnuplot-maverick', + 'aptly snapshot create snap2 empty', + 'aptly snapshot pull -no-deps -architectures=i386,amd64 snap2 snap1 snap3 gnuplot-x11', + 'aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick snap1 azure:test1:', + ] + runCmd = 'aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick azure:test1: snap3' + + def check(self): + super(AzurePublish3Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/binary-amd64/Packages') + self.check_exists('public/dists/maverick/main/binary-amd64/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-amd64/Packages.bz2') + + self.check_exists( + 'public/pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb' + ) + self.check_exists( + 'public/pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb' + ) + self.check_not_exists( + 'public/pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb' + ) + self.check_not_exists( + 'public/pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_amd64.deb' + ) + + # verify contents except of sums + self.check_file_contents( + 'public/dists/maverick/Release', 'release', match_prepare=strip_processor + ) + self.check_file_contents( + 'public/dists/maverick/main/binary-i386/Packages', + 'binary', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + + +class AzurePublish4Test(AzureTest): + """ + publish to Azure: multiple repos, list + """ + + fixtureCmds = [ + 'aptly repo create -distribution=maverick local-repo', + 'aptly repo add local-repo ${udebs}', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo azure:test1:', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=xyz local-repo azure:test1:', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo azure:test1:prefix', + ] + runCmd = 'aptly publish list' + + +class AzurePublish5Test(AzureTest): + """ + publish to Azure: publish drop - component cleanup + """ + + fixtureCmds = [ + 'aptly repo create local1', + 'aptly repo create local2', + 'aptly repo add local1 ${files}/libboost-program-options-dev_1.49.0.1_i386.deb', + 'aptly repo add local2 ${files}', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq1 local1 azure:test1:', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq2 local2 azure:test1:', + ] + runCmd = 'aptly publish drop sq2 azure:test1:' + + def check(self): + super(AzurePublish5Test, self).check() + + self.check_exists('public/dists/sq1') + self.check_not_exists('public/dists/sq2') + self.check_exists('public/pool/main/') + + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists( + 'public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb' + ) diff --git a/system/t06_publish/drop.py b/system/t06_publish/drop.py index 8c4978e7..3c361cc3 100644 --- a/system/t06_publish/drop.py +++ b/system/t06_publish/drop.py @@ -5,6 +5,7 @@ class PublishDrop1Test(BaseTest): """ publish drop: existing snapshot """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -25,6 +26,7 @@ class PublishDrop2Test(BaseTest): """ publish drop: under prefix """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -46,6 +48,7 @@ class PublishDrop3Test(BaseTest): """ publish drop: drop one distribution """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -68,6 +71,7 @@ class PublishDrop4Test(BaseTest): """ publish drop: drop one of components """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -91,6 +95,7 @@ class PublishDrop5Test(BaseTest): """ publish drop: component cleanup """ + requiresGPG2 = True fixtureCmds = [ "aptly repo create local1", "aptly repo create local2", @@ -128,6 +133,7 @@ class PublishDrop7Test(BaseTest): """ publish drop: under prefix with trailing & leading slashes """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -149,6 +155,7 @@ class PublishDrop8Test(BaseTest): """ publish drop: skip component cleanup """ + requiresGPG2 = True fixtureCmds = [ "aptly repo create local1", "aptly repo create local2", @@ -178,6 +185,7 @@ class PublishDrop9Test(BaseTest): """ publish drop: component cleanup after first cleanup skipped """ + requiresGPG2 = True fixtureCmds = [ "aptly repo create local1", "aptly repo create local2", diff --git a/system/t06_publish/list.py b/system/t06_publish/list.py index d5ad7c1f..408edcb2 100644 --- a/system/t06_publish/list.py +++ b/system/t06_publish/list.py @@ -12,6 +12,7 @@ class PublishList2Test(BaseTest): """ publish list: several repos list """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -30,6 +31,7 @@ class PublishList3Test(BaseTest): """ publish list: several repos list, raw """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ @@ -54,6 +56,7 @@ class PublishList5Test(BaseTest): """ publish list json: several repos list """ + requiresGPG2 = True fixtureDB = True fixturePool = True fixtureCmds = [ diff --git a/system/t06_publish/repo.py b/system/t06_publish/repo.py index fd3409d6..f882a428 100644 --- a/system/t06_publish/repo.py +++ b/system/t06_publish/repo.py @@ -671,12 +671,12 @@ class PublishRepo26Test(BaseTest): """ publish repo: sign with passphrase """ - skipTest = "Failing on CI" fixtureCmds = [ "aptly repo create local-repo", "aptly repo add local-repo ${files}", + "gpg --import --batch --passphrase verysecret ${files}/aptly_passphrase.sec" ] - runCmd = "aptly publish repo -keyring=${files}/aptly_passphrase.pub -secret-keyring=${files}/aptly_passphrase.sec -passphrase=verysecret -distribution=maverick local-repo" + runCmd = "aptly publish repo -batch -keyring=${files}/aptly_passphrase.pub -passphrase=verysecret -distribution=maverick local-repo" gold_processor = BaseTest.expand_environ def outputMatchPrepare(_, s): @@ -888,3 +888,66 @@ class PublishRepo33Test(BaseTest): self.check_exists('public/dists/maverick/main/binary-amd64/Packages') self.check_exists('public/dists/maverick/main/binary-amd64/Packages.gz') self.check_not_exists('public/dists/maverick/main/binary-amd64/Packages.bz2') + + +class PublishRepo34Test(BaseTest): + """ + publish repo: skeleton files + """ + fixtureCmds = [ + "aptly repo create local-repo", + "aptly repo add local-repo ${files}" + ] + runCmd = "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -skip-contents local-repo" + gold_processor = BaseTest.expand_environ + + def prepare_fixture(self): + super(PublishRepo34Test, self).prepare_fixture() + + self.write_file(os.path.join('skel', 'dists', 'maverick', 'main', 'dep11', 'README'), 'README test file') + self.write_file(os.path.join('skel', 'dists', 'maverick', 'Release'), 'Release test file') + + def check(self): + super(PublishRepo34Test, self).check() + + self.check_exists('public/dists/maverick/main/dep11/README') + + self.check_exists('public/dists/maverick/Release') + + readme = self.read_file('public/dists/maverick/main/dep11/README') + if readme != 'README test file': + raise Exception("README file not copied on publish") + + release = self.read_file('public/dists/maverick/Release') + if release == 'Release test file': + raise Exception("Release file was copied on publish") + + release = self.read_file('public/dists/maverick/Release').split("\n") + release = [l for l in release if l.startswith(" ")] + pathsSeen = set() + for l in release: + fileHash, fileSize, path = l.split() + pathsSeen.add(path) + + fileSize = int(fileSize) + + st = os.stat(os.path.join(os.environ["HOME"], ".aptly", 'public/dists/maverick/', path)) + if fileSize != st.st_size: + raise Exception("file size doesn't match for %s: %d != %d" % (path, fileSize, st.st_size)) + + if len(fileHash) == 32: + h = hashlib.md5() + elif len(fileHash) == 40: + h = hashlib.sha1() + elif len(fileHash) == 64: + h = hashlib.sha256() + else: + h = hashlib.sha512() + + h.update(self.read_file(os.path.join('public/dists/maverick', path), mode='b')) + + if h.hexdigest() != fileHash: + raise Exception("file hash doesn't match for %s: %s != %s" % (path, fileHash, h.hexdigest())) + + if 'main/dep11/README' not in pathsSeen: + raise Exception("README file not included in release file") diff --git a/system/t06_publish/show.py b/system/t06_publish/show.py index 7616b78e..a6f06824 100644 --- a/system/t06_publish/show.py +++ b/system/t06_publish/show.py @@ -51,3 +51,17 @@ class PublishShow4Test(BaseTest): "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1 ppa/smira", ] runCmd = "aptly publish show -json maverick ppa/smira" + + +class PublishShow5Test(BaseTest): + """ + publish show: existing local repo + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly repo create -distribution=wheezy local-repo", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -architectures=i386 local-repo" + ] + runCmd = "aptly publish show wheezy" + gold_processor = BaseTest.expand_environ diff --git a/system/t06_publish/snapshot.py b/system/t06_publish/snapshot.py index 8f4e3088..2ba245b4 100644 --- a/system/t06_publish/snapshot.py +++ b/system/t06_publish/snapshot.py @@ -1,6 +1,7 @@ -import os import hashlib import inspect +import os + from lib import BaseTest, ungzip_if_required @@ -982,7 +983,7 @@ class PublishSnapshot35Test(BaseTest): configOverride = {"max-tries": 1} fixtureGpg = True fixtureCmds = [ - "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (gnupg2)' -with-udebs stretch http://cdn-fastly.deb.debian.org/debian/ stretch main non-free", + "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (gnupg2)' -with-udebs stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main non-free", "aptly mirror update -keyring=aptlytest.gpg stretch", "aptly snapshot create stretch from mirror stretch", ] @@ -1139,7 +1140,7 @@ class PublishSnapshot37Test(BaseTest): configOverride = {"max-tries": 1} fixtureGpg = True fixtureCmds = [ - "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (gnupg2)' -with-udebs stretch http://cdn-fastly.deb.debian.org/debian/ stretch main non-free", + "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (gnupg2)' -with-udebs stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main non-free", "aptly mirror update -keyring=aptlytest.gpg stretch", "aptly mirror update -keyring=aptlytest.gpg stretch", "aptly snapshot create stretch from mirror stretch", @@ -1155,7 +1156,7 @@ class PublishSnapshot38Test(BaseTest): configOverride = {"max-tries": 1} fixtureGpg = True fixtureCmds = [ - "aptly -architectures=s390x mirror create -keyring=aptlytest.gpg -filter='installer' -with-installer stretch http://cdn-fastly.deb.debian.org/debian/ stretch main", + "aptly -architectures=s390x mirror create -keyring=aptlytest.gpg -filter='installer' -with-installer stretch http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch main", "aptly mirror update -keyring=aptlytest.gpg stretch", "aptly snapshot create stretch from mirror stretch", ] @@ -1239,3 +1240,147 @@ class PublishSnapshot40Test(BaseTest): self.check_exists('public/dists/maverick/main/binary-amd64/Packages') self.check_exists('public/dists/maverick/main/binary-amd64/Packages.gz') self.check_not_exists('public/dists/maverick/main/binary-amd64/Packages.bz2') + + +class PublishSnapshot41Test(BaseTest): + """ + publish snapshot: mirror with / in distribution + """ + configOverride = {"max-tries": 1} + fixtureGpg = True + fixtureCmds = [ + "aptly -architectures='i386' mirror create -keyring=aptlytest.gpg -with-sources -filter='nginx | Priority (required)'" # continued on next line + " -filter-with-deps=true ps41 http://repo.aptly.info/system-tests/security.debian.org/debian-security buster/updates main", + "aptly mirror update -keyring=aptlytest.gpg ps41", + "aptly snapshot create snap41 from mirror ps41", + ] + runCmd = "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap41" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishSnapshot41Test, self).check() + + self.check_exists('public/dists/buster/updates/Release') + self.check_exists('public/dists/buster/updates/Release.gpg') + self.check_exists('public/dists/buster/updates/InRelease') + self.check_exists('public/dists/buster/updates/main/source/Release') + self.check_exists('public/dists/buster/updates/main/source/Sources') + self.check_exists('public/dists/buster/updates/main/source/Sources.gz') + self.check_exists('public/dists/buster/updates/main/source/Sources.bz2') + self.check_exists('public/dists/buster/updates/main/binary-i386/Packages') + self.check_exists('public/dists/buster/updates/main/binary-i386/Packages.gz') + self.check_exists('public/dists/buster/updates/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/buster/updates/main/binary-i386/Release') + self.check_exists('public/dists/buster/updates/main/Contents-i386.gz') + self.check_exists('public/dists/buster/updates/Contents-i386.gz') + self.check_exists('public/pool/main/u/util-linux/bsdutils_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/fdisk_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/libblkid1_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/libfdisk1_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/libmount1_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/libsmartcols1_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/libuuid1_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/mount_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1-0.1+deb10u1_i386.deb') + self.check_exists('public/pool/main/d/dpkg/dpkg_1.19.8_i386.deb') + self.check_exists('public/pool/main/e/e2fsprogs/e2fslibs_1.44.5-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/e/e2fsprogs/libcom-err2_1.44.5-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/e/e2fsprogs/libcomerr2_1.44.5-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/e/e2fsprogs/libext2fs2_1.44.5-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/e/e2fsprogs/libss2_1.44.5-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/g/gzip/gzip_1.9-3+deb10u1_i386.deb') + self.check_exists('public/pool/main/g/glibc/libc-bin_2.28-10+deb10u2_i386.deb') + self.check_exists('public/pool/main/g/glibc/libc6_2.28-10+deb10u2_i386.deb') + self.check_exists('public/pool/main/g/glibc/multiarch-support_2.28-10+deb10u2_i386.deb') + self.check_exists('public/pool/main/b/bzip2/libbz2-1.0_1.0.6-9.2~deb10u2_i386.deb') + self.check_exists('public/pool/main/f/freetype/libfreetype6_2.9.1-3+deb10u2_i386.deb') + self.check_exists('public/pool/main/libg/libgd2/libgd3_2.2.5-5.2+deb10u1_i386.deb') + self.check_exists('public/pool/main/i/icu/libicu63_63.1-6+deb10u2_i386.deb') + self.check_exists('public/pool/main/l/lz4/liblz4-1_1.8.3-1+deb10u1_i386.deb') + self.check_exists('public/pool/main/x/xz-utils/liblzma5_5.2.4-1+deb10u1_i386.deb') + self.check_exists('public/pool/main/n/ncurses/libncursesw6_6.1+20181013-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/ncurses/libtinfo5_6.1+20181013-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/ncurses/libtinfo6_6.1+20181013-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/ncurses/ncurses-base_6.1+20181013-2+deb10u5_all.deb') + self.check_exists('public/pool/main/n/ncurses/ncurses-bin_6.1+20181013-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-auth-pam_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-cache-purge_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-dav-ext_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-echo_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-fancyindex_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-geoip_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-headers-more-filter_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-image-filter_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-lua_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-ndk_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-perl_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-subs-filter_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-uploadprogress_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-upstream-fair_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-http-xslt-filter_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-mail_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-nchan_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/libnginx-mod-stream_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/nginx_1.14.2-2+deb10u5_all.deb') + self.check_exists('public/pool/main/n/nginx/nginx_1.14.2-2+deb10u5.debian.tar.xz') + self.check_exists('public/pool/main/n/nginx/nginx_1.14.2-2+deb10u5.dsc') + self.check_exists('public/pool/main/n/nginx/nginx_1.14.2.orig.tar.gz') + self.check_exists('public/pool/main/n/nginx/nginx-common_1.14.2-2+deb10u5_all.deb') + self.check_exists('public/pool/main/n/nginx/nginx-extras_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/nginx-full_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/n/nginx/nginx-light_1.14.2-2+deb10u5_i386.deb') + self.check_exists('public/pool/main/o/openssl/libssl1.1_1.1.1n-0+deb10u6_i386.deb') + self.check_exists('public/pool/main/s/systemd/libsystemd0_241-7~deb10u10_i386.deb') + self.check_exists('public/pool/main/s/systemd/libudev1_241-7~deb10u10_i386.deb') + self.check_exists('public/pool/main/t/tiff/libtiff5_4.1.0+git191117-2~deb10u9_i386.deb') + self.check_exists('public/pool/main/t/tar/tar_1.30+dfsg-6+deb10u1_i386.deb') + self.check_exists('public/pool/main/t/tzdata/tzdata_2021a-0+deb10u12_all.deb') + self.check_exists('public/pool/main/libw/libwebp/libwebp6_0.6.1-2+deb10u3_i386.deb') + self.check_exists('public/pool/main/libx/libx11/libx11-6_1.6.7-1+deb10u4_i386.deb') + self.check_exists('public/pool/main/libx/libx11/libx11-data_1.6.7-1+deb10u4_all.deb') + self.check_exists('public/pool/main/libx/libxml2/libxml2_2.9.4+dfsg1-7+deb10u6_i386.deb') + self.check_exists('public/pool/main/libx/libxpm/libxpm4_3.5.12-1+deb10u2_i386.deb') + self.check_exists('public/pool/main/libx/libxslt/libxslt1.1_1.1.32-2.2~deb10u2_i386.deb') + self.check_exists('public/pool/main/libz/libzstd/libzstd1_1.3.8+dfsg-3+deb10u2_i386.deb') + self.check_exists('public/pool/main/z/zlib/zlib1g_1.2.11.dfsg-1+deb10u2_i386.deb') + # check source packages with different names + self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1-0.1+deb10u1.dsc') + self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1-0.1+deb10u1.debian.tar.xz') + self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1.orig.tar.xz') + self.check_exists('public/pool/main/g/glibc/glibc_2.28-10+deb10u2.debian.tar.xz') + self.check_exists('public/pool/main/g/glibc/glibc_2.28-10+deb10u2.dsc') + self.check_exists('public/pool/main/g/glibc/glibc_2.28.orig.tar.xz') + self.check_exists('public/pool/main/n/ncurses/ncurses_6.1+20181013-2+deb10u5.debian.tar.xz') + self.check_exists('public/pool/main/n/ncurses/ncurses_6.1+20181013-2+deb10u5.dsc') + self.check_exists('public/pool/main/n/ncurses/ncurses_6.1+20181013.orig.tar.gz') + self.check_exists('public/pool/main/z/zlib/zlib_1.2.11.dfsg-1+deb10u2.debian.tar.xz') + self.check_exists('public/pool/main/z/zlib/zlib_1.2.11.dfsg-1+deb10u2.dsc') + self.check_exists('public/pool/main/z/zlib/zlib_1.2.11.dfsg.orig.tar.gz') + self.check_exists('public/pool/main/x/xz-utils/xz-utils_5.2.4-1+deb10u1.debian.tar.xz') + self.check_exists('public/pool/main/x/xz-utils/xz-utils_5.2.4-1+deb10u1.dsc') + self.check_exists('public/pool/main/x/xz-utils/xz-utils_5.2.4.orig.tar.xz') + self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5-1+deb10u2.debian.tar.xz') + self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5-1+deb10u2.dsc') + self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5.orig.tar.gz') + self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5.orig.tar.gz.asc') + + +class PublishSnapshot42Test(BaseTest): + """ + publish snapshot: mirror with multi-dist + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + ] + runCmd = "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -multi-dist snap1" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishSnapshot42Test, self).check() + self.check_not_exists( + 'public/pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb') + self.check_exists( + 'public/pool/maverick/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb') diff --git a/system/t06_publish/source.py b/system/t06_publish/source.py new file mode 100644 index 00000000..78c81c60 --- /dev/null +++ b/system/t06_publish/source.py @@ -0,0 +1,218 @@ +from lib import BaseTest + + +class PublishSourceAdd1Test(BaseTest): + """ + publish source add: add single source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source add -component=test maverick snap2" + gold_processor = BaseTest.expand_environ + + +class PublishSourceAdd2Test(BaseTest): + """ + publish source add: add multiple sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source add -component=test,other-test maverick snap2 snap3" + gold_processor = BaseTest.expand_environ + + +class PublishSourceAdd3Test(BaseTest): + """ + publish source add: (re-)add already added source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source add -component=main maverick snap2" + expectedCode = 1 + gold_processor = BaseTest.expand_environ + + +class PublishSourceList1Test(BaseTest): + """ + publish source list: show source changes + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + "aptly publish source add -component=test maverick snap2", + ] + runCmd = "aptly publish source list maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceList2Test(BaseTest): + """ + publish source list: show source changes as JSON + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + "aptly publish source add -component=test maverick snap2", + ] + runCmd = "aptly publish source list -json maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceList3Test(BaseTest): + """ + publish source list: show source changes (empty) + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source list maverick" + expectedCode = 1 + gold_processor = BaseTest.expand_environ + + +class PublishSourceDrop1Test(BaseTest): + """ + publish source drop: drop source changes + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source drop maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceUpdate1Test(BaseTest): + """ + publish source update: Update single source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source update -component=main maverick snap2" + gold_processor = BaseTest.expand_environ + + +class PublishSourceUpdate2Test(BaseTest): + """ + publish source update: Update multiple sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test snap1 snap2", + ] + runCmd = "aptly publish source update -component=main,test maverick snap2 snap3" + gold_processor = BaseTest.expand_environ + + +class PublishSourceUpdate3Test(BaseTest): + """ + publish source update: Update not existing source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source update -component=not-existent maverick snap1" + gold_processor = BaseTest.expand_environ + + +class PublishSourceReplace1Test(BaseTest): + """ + publish source replace: Replace existing sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test snap1 snap2", + ] + runCmd = "aptly publish source replace -component=main-new,test-new maverick snap2 snap3" + gold_processor = BaseTest.expand_environ + + +class PublishSourceRemove1Test(BaseTest): + """ + publish source remove: Remove single source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test snap1 snap2", + ] + runCmd = "aptly publish source remove -component=test maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceRemove2Test(BaseTest): + """ + publish source remove: Remove multiple sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test,other-test snap1 snap2 snap3", + ] + runCmd = "aptly publish source remove -component=test,other-test maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceRemove3Test(BaseTest): + """ + publish source remove: Remove not-existing source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source remove -component=not-existent maverick" + expectedCode = 1 + gold_processor = BaseTest.expand_environ diff --git a/system/t06_publish/switch.py b/system/t06_publish/switch.py index 6a86a3cc..4243a978 100644 --- a/system/t06_publish/switch.py +++ b/system/t06_publish/switch.py @@ -574,3 +574,32 @@ class PublishSwitch15Test(BaseTest): self.check_exists('public/dists/maverick/main/binary-amd64/Packages') self.check_exists('public/dists/maverick/main/binary-amd64/Packages.gz') self.check_not_exists('public/dists/maverick/main/binary-amd64/Packages.bz2') + + +class PublishSwitch16Test(BaseTest): + """ + publish switch: -multi-dist + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot pull -no-deps -architectures=i386,amd64 snap2 snap1 snap3 gnuplot-x11", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=bookworm snap1", + ] + runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -multi-dist bookworm snap3" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishSwitch16Test, self).check() + + self.check_exists('public/dists/bookworm/Release') + + self.check_exists('public/dists/bookworm/main/binary-i386/Packages') + self.check_exists('public/dists/bookworm/main/binary-i386/Packages.gz') + + self.check_exists('public/dists/bookworm/main/binary-amd64/Packages') + self.check_exists('public/dists/bookworm/main/binary-amd64/Packages.gz') + + self.check_exists('public/pool/bookworm/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb') diff --git a/system/t06_publish/update.py b/system/t06_publish/update.py index d3cb443f..3a24ec16 100644 --- a/system/t06_publish/update.py +++ b/system/t06_publish/update.py @@ -175,39 +175,6 @@ class PublishUpdate3Test(BaseTest): self.check_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') -class PublishUpdate4Test(BaseTest): - """ - publish update: added some packages, but list of published archs doesn't change - """ - fixtureCmds = [ - "aptly repo create local-repo", - "aptly repo add local-repo ${files}/pyspi_0.6.1-1.3.dsc", - "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick local-repo", - "aptly repo add local-repo ${files}/libboost-program-options-dev_1.49.0.1_i386.deb" - ] - runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" - gold_processor = BaseTest.expand_environ - - def check(self): - super(PublishUpdate4Test, self).check() - - self.check_exists('public/dists/maverick/InRelease') - self.check_exists('public/dists/maverick/Release') - self.check_exists('public/dists/maverick/Release.gpg') - - self.check_not_exists('public/dists/maverick/main/binary-i386/Packages') - self.check_not_exists('public/dists/maverick/main/binary-i386/Packages.gz') - self.check_not_exists('public/dists/maverick/main/binary-i386/Packages.bz2') - self.check_exists('public/dists/maverick/main/source/Sources') - self.check_exists('public/dists/maverick/main/source/Sources.gz') - self.check_exists('public/dists/maverick/main/source/Sources.bz2') - - self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') - self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') - self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') - self.check_not_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') - - class PublishUpdate5Test(BaseTest): """ publish update: no such publish @@ -216,20 +183,6 @@ class PublishUpdate5Test(BaseTest): expectedCode = 1 -class PublishUpdate6Test(BaseTest): - """ - publish update: not a local repo - """ - fixtureDB = True - fixturePool = True - fixtureCmds = [ - "aptly snapshot create snap1 from mirror gnuplot-maverick", - "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1", - ] - runCmd = "aptly publish update maverick" - expectedCode = 1 - - class PublishUpdate7Test(BaseTest): """ publish update: multiple components, add some packages @@ -462,3 +415,194 @@ class PublishUpdate13Test(BaseTest): self.check_exists('public/dists/maverick/main/binary-i386/Packages') self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') self.check_not_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + + +class PublishUpdate14Test(BaseTest): + """ + publish update: -multi-dist + """ + fixtureCmds = [ + "aptly repo create local-repo", + "aptly repo add local-repo ${files}/", + "aptly publish repo -keyring=${files}/aptly.pub -distribution=bookworm local-repo", + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -multi-dist bookworm" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate14Test, self).check() + + self.check_exists('public/dists/bookworm/InRelease') + self.check_exists('public/dists/bookworm/Release') + self.check_exists('public/dists/bookworm/Release.gpg') + + self.check_exists('public/dists/bookworm/main/binary-i386/Packages') + self.check_exists('public/dists/bookworm/main/binary-i386/Packages.gz') + + self.check_exists('public/pool/bookworm/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + + +class PublishUpdate15Test(BaseTest): + """ + publish update: source added + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main snap1", + "aptly publish source add -component=test,other-test maverick snap2 snap3" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate15Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + + self.check_exists('public/dists/maverick/test/binary-i386/Packages') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.bz2') + + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.bz2') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['main', 'other-test', 'test'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'main other-test test')) + + +class PublishUpdate16Test(BaseTest): + """ + publish update: source removed + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main,test,other-test snap1 snap2 snap3", + "aptly publish source remove -component=test,other-test maverick" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate16Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/Contents-i386.gz') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['main'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'main')) + + +class PublishUpdate17Test(BaseTest): + """ + publish update: source updated + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly snapshot create snap4 from mirror gnuplot-maverick", + "aptly snapshot create snap5 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main,test,other-test snap1 snap2 snap3", + "aptly publish source update -component=test,other-test maverick snap4 snap5" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate17Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/Contents-i386.gz') + + self.check_exists('public/dists/maverick/test/binary-i386/Packages') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/test/Contents-i386.gz') + + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/other-test/Contents-i386.gz') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['main', 'other-test', 'test'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'main other-test test')) + + +class PublishUpdate18Test(BaseTest): + """ + publish update: source added, updated and removed + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main,test snap1 snap2", + "aptly publish source remove -component=main maverick", + "aptly publish source update -component=test maverick snap3", + "aptly publish source add -component=other-test maverick snap1" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate18Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/test/binary-i386/Packages') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/test/Contents-i386.gz') + + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/other-test/Contents-i386.gz') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['other-test', 'test'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'other-test test')) diff --git a/system/t07_serve/__init__.py b/system/t07_serve/__init__.py index 457b9f59..1d3d2f7c 100644 --- a/system/t07_serve/__init__.py +++ b/system/t07_serve/__init__.py @@ -1,81 +1,3 @@ """ Testing serving public repo """ - -import http.client -import os -import signal -import subprocess -import shlex -import time - -from lib import BaseTest - - -class RootDirInaccessible(BaseTest): - """ - serve command aborts if rootDir is inaccessible - """ - skipTest = 'User is root' if os.environ['USER'] == 'root' else False - fixtureDB = False - fixturePool = False - - configOverride = { - "rootDir": "/root" # any directory that exists but is not writable - } - - runCmd = "aptly serve -listen=127.0.0.1:8765" - expectedCode = 1 - - -class Serve1Test(BaseTest): - """ - serve public: two publishes, verify HTTP - """ - fixtureDB = True - fixturePool = True - fixtureCmds = [ - "aptly snapshot create snap1 from mirror gnuplot-maverick", - "aptly snapshot create snap2 from mirror gnuplot-maverick-src", - "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1", - "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap2 debian", - "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -component=main,contrib snap1 snap2 multi", - ] - runCmd = "aptly serve -listen=127.0.0.1:8765" - - def run(self): - try: - proc = subprocess.Popen(shlex.split(self.runCmd), stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=0) - - try: - time.sleep(1) - - conn = http.client.HTTPConnection("127.0.0.1", 8765) - conn.request("GET", "/") - r = conn.getresponse() - if r.status != 200: - raise Exception("Expected status 200 != %d" % r.status) - self.http_response = r.read() - - output = os.read(proc.stdout.fileno(), 8192) - - finally: - proc.send_signal(signal.SIGINT) - proc.wait() - - if proc.returncode != -2 and proc.returncode != 2: - raise Exception("exit code %d != %d (output: %s)" % (proc.returncode, -2, output)) - self.output = output - except Exception as e: - raise Exception("Running command %s failed: %s" % (self.runCmd, str(e))) - - def check(self): - self.check_output() - self.verify_match(self.get_gold('http'), self.http_response, match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) - - -class Serve2Test(BaseTest): - """ - serve public: no publishes - """ - runCmd = "aptly serve -listen=127.0.0.1:8765" diff --git a/system/t07_serve/serve.py b/system/t07_serve/serve.py new file mode 100644 index 00000000..4f473f3b --- /dev/null +++ b/system/t07_serve/serve.py @@ -0,0 +1,81 @@ +""" +Testing serving public repo +""" + +import http.client +import os +import signal +import subprocess +import shlex +import time + +from lib import BaseTest + + +class RootDirInaccessible(BaseTest): + """ + serve command aborts if rootDir is inaccessible + """ + skipTest = 'User is root' if os.environ['USER'] == 'root' else False + fixtureDB = False + fixturePool = False + + configOverride = { + "rootDir": "/root" # any directory that exists but is not writable + } + + runCmd = "aptly serve -listen=127.0.0.1:8765" + expectedCode = 1 + + +class Serve1Test(BaseTest): + """ + serve public: two publishes, verify HTTP + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 from mirror gnuplot-maverick-src", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap2 debian", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -component=main,contrib snap1 snap2 multi", + ] + runCmd = "../aptly.test serve -listen=127.0.0.1:8765" + + def run(self): + try: + proc = subprocess.Popen(shlex.split(self.runCmd), stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=0) + + try: + time.sleep(1) + + conn = http.client.HTTPConnection("127.0.0.1", 8765) + conn.request("GET", "/") + r = conn.getresponse() + if r.status != 200: + raise Exception("Expected status 200 != %d" % r.status) + self.http_response = r.read() + + output = os.read(proc.stdout.fileno(), 8192) + + finally: + proc.send_signal(signal.SIGINT) + proc.wait() + + if proc.returncode != -2 and proc.returncode != 2: + raise Exception("exit code %d != %d (output: %s)" % (proc.returncode, -2, output)) + self.output = output + except Exception as e: + raise Exception("Running command %s failed: %s" % (self.runCmd, str(e))) + + def check(self): + self.check_output() + self.verify_match(self.get_gold('http'), self.http_response, match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class Serve2Test(BaseTest): + """ + serve public: no publishes + """ + runCmd = "aptly serve -listen=127.0.0.1:8765" diff --git a/system/t08_db/CleanupDB9Test_publish_drop b/system/t08_db/CleanupDB9Test_publish_drop index c9303809..ee4bb6e3 100644 --- a/system/t08_db/CleanupDB9Test_publish_drop +++ b/system/t08_db/CleanupDB9Test_publish_drop @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/def... -Cleaning up prefix "." components main... +Cleaning up published repository ./def... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t09_repo/AddRepo17Test/mesa-stable-no-march_24.2.6-101pika1_amd64.deb b/system/t09_repo/AddRepo17Test/mesa-stable-no-march_24.2.6-101pika1_amd64.deb new file mode 100644 index 00000000..b021ea8e Binary files /dev/null and b/system/t09_repo/AddRepo17Test/mesa-stable-no-march_24.2.6-101pika1_amd64.deb differ diff --git a/system/t09_repo/AddRepo17Test/mesa-stable_24.2.6-101pika1_amd64.deb b/system/t09_repo/AddRepo17Test/mesa-stable_24.2.6-101pika1_amd64.deb new file mode 100644 index 00000000..1114ba60 Binary files /dev/null and b/system/t09_repo/AddRepo17Test/mesa-stable_24.2.6-101pika1_amd64.deb differ diff --git a/system/t09_repo/AddRepo17Test_gold b/system/t09_repo/AddRepo17Test_gold new file mode 100644 index 00000000..687c9eb4 --- /dev/null +++ b/system/t09_repo/AddRepo17Test_gold @@ -0,0 +1,3 @@ +Loading packages... +[+] mesa-stable-no-march_24.2.6-101pika1_amd64 added +[+] mesa-stable_24.2.6-101pika1_amd64 added diff --git a/system/t09_repo/AddRepo17Test_repo_show b/system/t09_repo/AddRepo17Test_repo_show new file mode 100644 index 00000000..7be66585 --- /dev/null +++ b/system/t09_repo/AddRepo17Test_repo_show @@ -0,0 +1,8 @@ +Name: repo17 +Comment: Repo17 +Default Distribution: squeeze +Default Component: main +Number of packages: 2 +Packages: + mesa-stable_24.2.6-101pika1_amd64 + mesa-stable-no-march_24.2.6-101pika1_amd64 diff --git a/system/t09_repo/AddRepo8Test_gold b/system/t09_repo/AddRepo8Test_gold index f0cf6eee..51e9ca08 100644 --- a/system/t09_repo/AddRepo8Test_gold +++ b/system/t09_repo/AddRepo8Test_gold @@ -1,5 +1,5 @@ Loading packages... -[!] Unable to add package to repo pyspi_0.6.1-1.3_source: conflict in package pyspi_0.6.1-1.3_source +[!] Unable to add package: package already exists and is different: pyspi_0.6.1-1.3_source [!] Some files were skipped due to errors: /pyspi_0.6.1-1.3.conflict.dsc ERROR: some files failed to be added diff --git a/system/t09_repo/AzureRepoTest_gold b/system/t09_repo/AzureRepoTest_gold new file mode 100644 index 00000000..7ddaaa9f --- /dev/null +++ b/system/t09_repo/AzureRepoTest_gold @@ -0,0 +1,5 @@ +Loading packages... +[+] libboost-program-options-dev_1.49.0.1_i386 added +[+] libboost-program-options-dev_1.62.0.1_i386 added +[+] pyspi_0.6.1-1.4_source added +[+] pyspi_0.6.1-1.3_source added diff --git a/system/t09_repo/AzureRepoTest_repo_show b/system/t09_repo/AzureRepoTest_repo_show new file mode 100644 index 00000000..17dbc6ef --- /dev/null +++ b/system/t09_repo/AzureRepoTest_repo_show @@ -0,0 +1,10 @@ +Name: repo +Comment: Repo +Default Distribution: squeeze +Default Component: main +Number of packages: 4 +Packages: + libboost-program-options-dev_1.62.0.1_i386 + libboost-program-options-dev_1.49.0.1_i386 + pyspi_0.6.1-1.4_source + pyspi_0.6.1-1.3_source diff --git a/system/t09_repo/ImportRepo1Test_gold b/system/t09_repo/ImportRepo1Test_gold index c4fa29ca..9e7a9597 100644 --- a/system/t09_repo/ImportRepo1Test_gold +++ b/system/t09_repo/ImportRepo1Test_gold @@ -1,4 +1,13 @@ Loading packages... +[o] nginx-extras_1.2.1-2.2+wheezy2_amd64 imported +[o] nginx-extras_1.2.1-2.2+wheezy2_i386 imported +[o] nginx-full_1.2.1-2.2+wheezy2_amd64 imported +[o] nginx-full_1.2.1-2.2+wheezy2_i386 imported +[o] nginx-light_1.2.1-2.2+wheezy2_amd64 imported +[o] nginx-light_1.2.1-2.2+wheezy2_i386 imported +[o] nginx-naxsi-ui_1.2.1-2.2+wheezy2_all imported +[o] nginx-naxsi_1.2.1-2.2+wheezy2_amd64 imported +[o] nginx-naxsi_1.2.1-2.2+wheezy2_i386 imported [o] nginx_1.2.1-2.2+wheezy2_all imported [o] unpaper_0.4.2-1_amd64 imported \ No newline at end of file diff --git a/system/t09_repo/ImportRepo1Test_repo_show b/system/t09_repo/ImportRepo1Test_repo_show index daf99d40..8b71f6d0 100644 --- a/system/t09_repo/ImportRepo1Test_repo_show +++ b/system/t09_repo/ImportRepo1Test_repo_show @@ -2,11 +2,20 @@ Name: repo1 Comment: Cool Default Distribution: squeeze Default Component: main -Number of packages: 6 +Number of packages: 15 Packages: libboost-program-options-dev_1.62.0.1_i386 libboost-program-options-dev_1.49.0.1_i386 nginx_1.2.1-2.2+wheezy2_all + nginx-extras_1.2.1-2.2+wheezy2_amd64 + nginx-extras_1.2.1-2.2+wheezy2_i386 + nginx-full_1.2.1-2.2+wheezy2_amd64 + nginx-full_1.2.1-2.2+wheezy2_i386 + nginx-light_1.2.1-2.2+wheezy2_amd64 + nginx-light_1.2.1-2.2+wheezy2_i386 + nginx-naxsi_1.2.1-2.2+wheezy2_amd64 + nginx-naxsi_1.2.1-2.2+wheezy2_i386 + nginx-naxsi-ui_1.2.1-2.2+wheezy2_all pyspi_0.6.1-1.4_source pyspi_0.6.1-1.3_source unpaper_0.4.2-1_amd64 diff --git a/system/t09_repo/IncludeRepo13Test_gold b/system/t09_repo/IncludeRepo13Test_gold index 3a048076..410ff1e2 100644 --- a/system/t09_repo/IncludeRepo13Test_gold +++ b/system/t09_repo/IncludeRepo13Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository unstable for changes file hardlink_0.2.1_amd64.changes... [!] changes file skipped due to uploaders config: hardlink_0.2.1_amd64.changes, keys []pgp.Key{"21DBB89C16DB3E6D"}: denied as no rule matches diff --git a/system/t09_repo/IncludeRepo14Test_gold b/system/t09_repo/IncludeRepo14Test_gold index 58285bb6..13bad791 100644 --- a/system/t09_repo/IncludeRepo14Test_gold +++ b/system/t09_repo/IncludeRepo14Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository unstable for changes file hardlink_0.2.1_amd64.changes... [+] hardlink_0.2.1_source added diff --git a/system/t09_repo/IncludeRepo18Test_gold b/system/t09_repo/IncludeRepo18Test_gold index 58285bb6..13bad791 100644 --- a/system/t09_repo/IncludeRepo18Test_gold +++ b/system/t09_repo/IncludeRepo18Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository unstable for changes file hardlink_0.2.1_amd64.changes... [+] hardlink_0.2.1_source added diff --git a/system/t09_repo/IncludeRepo19Test_gold b/system/t09_repo/IncludeRepo19Test_gold index 3a048076..410ff1e2 100644 --- a/system/t09_repo/IncludeRepo19Test_gold +++ b/system/t09_repo/IncludeRepo19Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository unstable for changes file hardlink_0.2.1_amd64.changes... [!] changes file skipped due to uploaders config: hardlink_0.2.1_amd64.changes, keys []pgp.Key{"21DBB89C16DB3E6D"}: denied as no rule matches diff --git a/system/t09_repo/IncludeRepo1Test_gold b/system/t09_repo/IncludeRepo1Test_gold index 456eb0b5..13bad791 100644 --- a/system/t09_repo/IncludeRepo1Test_gold +++ b/system/t09_repo/IncludeRepo1Test_gold @@ -1,4 +1,5 @@ -gpgv: Signature made Sun Mar 15 20:36:44 2015 MSK using DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository unstable for changes file hardlink_0.2.1_amd64.changes... [+] hardlink_0.2.1_source added diff --git a/system/t09_repo/IncludeRepo21Test_gold b/system/t09_repo/IncludeRepo21Test_gold index ab43a776..eee0a5d7 100644 --- a/system/t09_repo/IncludeRepo21Test_gold +++ b/system/t09_repo/IncludeRepo21Test_gold @@ -1,4 +1,4 @@ -[!] unable to process file hardlink_0.2.1_amd64.changes: failed to verify signature: openpgp: invalid signature: hash tag doesn't match +[!] unable to process file hardlink_0.2.1_amd64.changes: failed to verify signature: openpgp: invalid signature: DSA verification failure [!] Some files were skipped due to errors: /01/hardlink_0.2.1_amd64.changes ERROR: some files failed to be added diff --git a/system/t09_repo/IncludeRepo2Test_gold b/system/t09_repo/IncludeRepo2Test_gold index a9f191f2..f4399d73 100644 --- a/system/t09_repo/IncludeRepo2Test_gold +++ b/system/t09_repo/IncludeRepo2Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository my-unstable for changes file hardlink_0.2.1_amd64.changes... [+] hardlink_0.2.1_source added diff --git a/system/t09_repo/IncludeRepo3Test_gold b/system/t09_repo/IncludeRepo3Test_gold index 96cda590..47fd79f8 100644 --- a/system/t09_repo/IncludeRepo3Test_gold +++ b/system/t09_repo/IncludeRepo3Test_gold @@ -1 +1 @@ -ERROR: error parsing -repo template: template: repo:1: unexpected "}" in operand; missing space? +ERROR: error parsing -repo template: template: repo:1: bad character U+007D '}'; missing space? diff --git a/system/t09_repo/IncludeRepo5Test_gold b/system/t09_repo/IncludeRepo5Test_gold index 58285bb6..13bad791 100644 --- a/system/t09_repo/IncludeRepo5Test_gold +++ b/system/t09_repo/IncludeRepo5Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " Loading repository unstable for changes file hardlink_0.2.1_amd64.changes... [+] hardlink_0.2.1_source added diff --git a/system/t09_repo/IncludeRepo6Test_gold b/system/t09_repo/IncludeRepo6Test_gold index 9c02bfa6..162a2090 100644 --- a/system/t09_repo/IncludeRepo6Test_gold +++ b/system/t09_repo/IncludeRepo6Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " [!] unable to process file hardlink_0.2.1_amd64.changes: open /01/hardlink_0.2.1.tar.gz: no such file or directory [!] Some files were skipped due to errors: diff --git a/system/t09_repo/IncludeRepo7Test_gold b/system/t09_repo/IncludeRepo7Test_gold index 2215c9dd..a1af0721 100644 --- a/system/t09_repo/IncludeRepo7Test_gold +++ b/system/t09_repo/IncludeRepo7Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: Good signature from "Aptly Tester (don't use it) " [!] unable to process file hardlink_0.2.1_amd64.changes: checksum mismatch MD5: expected 4efce26825af5842f43961096dd890b3 != obtained 7515e9279fc32d0c89db965f4dfdec8c [!] Some files were skipped due to errors: diff --git a/system/t09_repo/IncludeRepo8Test_gold b/system/t09_repo/IncludeRepo8Test_gold index 1f1e0949..042c53e3 100644 --- a/system/t09_repo/IncludeRepo8Test_gold +++ b/system/t09_repo/IncludeRepo8Test_gold @@ -1,4 +1,5 @@ -gpgv: DSA key ID 16DB3E6D +gpgv: Signature made Sun Mar 15 17:36:44 2015 UTC +gpgv: using DSA key 21DBB89C16DB3E6D gpgv: BAD signature from "Aptly Tester (don't use it) " [!] unable to process file hardlink_0.2.1_amd64.changes: verification of clearsigned file failed: exit status 1 [!] Some files were skipped due to errors: diff --git a/system/t09_repo/add.py b/system/t09_repo/add.py index 61c64d1e..08274f77 100644 --- a/system/t09_repo/add.py +++ b/system/t09_repo/add.py @@ -344,3 +344,21 @@ class AddRepo16Test(BaseTest): self.check_cmd_output("aptly repo show repo2", "repo_show") shutil.rmtree(self.tempSrcDir) + + +class AddRepo17Test(BaseTest): + """ + add packages to local repo: .deb file with provides + """ + fixtureCmds = [ + "aptly repo create -comment=Repo17 -distribution=squeeze repo17", + ] + runCmd = "aptly repo add repo17 ${testfiles}/mesa-stable_24.2.6-101pika1_amd64.deb ${testfiles}/mesa-stable-no-march_24.2.6-101pika1_amd64.deb" + + def check(self): + self.check_output() + self.check_cmd_output("aptly repo show -with-packages repo17", "repo_show") + + # check pool + self.check_exists('pool/e3/f6/51297bd4bd0ef999296ef0a28299_mesa-stable-no-march_24.2.6-101pika1_amd64.deb') + self.check_exists('pool/01/6b/3d864229761eff49a8680c9987ab_mesa-stable_24.2.6-101pika1_amd64.deb') diff --git a/system/t09_repo/azure.py b/system/t09_repo/azure.py new file mode 100644 index 00000000..7506c781 --- /dev/null +++ b/system/t09_repo/azure.py @@ -0,0 +1,42 @@ +from azure_lib import AzureTest + + +class AzureRepoTest(AzureTest): + """ + Azure: add directory to repo + """ + + fixtureCmds = [ + 'aptly repo create -comment=Repo -distribution=squeeze repo', + ] + runCmd = 'aptly repo add repo ${files}' + + use_azure_pool = True + + def prepare(self): + super(AzureRepoTest, self).prepare() + + self.configOverride['packagePoolStorage'] = { + 'azure': self.azure_endpoint, + } + + def check(self): + self.check_output() + self.check_cmd_output('aptly repo show -with-packages repo', 'repo_show') + + # check pool + self.check_exists_azure_only( + 'c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb' + ) + self.check_exists_azure_only( + '2e/77/0b28df948f3197ed0b679bdea99f_pyspi_0.6.1-1.3.diff.gz' + ) + self.check_exists_azure_only( + 'd4/94/aaf526f1ec6b02f14c2f81e060a5_pyspi_0.6.1-1.3.dsc' + ) + self.check_exists_azure_only( + '64/06/9ee828c50b1c597d10a3fefbba27_pyspi_0.6.1.orig.tar.gz' + ) + self.check_exists_azure_only( + '28/9d/3aefa970876e9c43686ce2b02f47_pyspi-0.6.1-1.3.stripped.dsc' + ) diff --git a/system/t09_repo/cmdimport.py b/system/t09_repo/cmdimport.py index 5a4337ba..e23510db 100644 --- a/system/t09_repo/cmdimport.py +++ b/system/t09_repo/cmdimport.py @@ -83,7 +83,7 @@ class ImportRepo6Test(BaseTest): """ fixtureCmds = [ "aptly repo create -comment=Cool -distribution=squeeze repo1", - "aptly mirror create --ignore-signatures mirror1 http://cdn-fastly.deb.debian.org/debian/ stretch", + "aptly mirror create --ignore-signatures mirror1 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch", ] runCmd = "aptly repo import mirror1 repo1 nginx" expectedCode = 1 diff --git a/system/t12_api/docs.py b/system/t12_api/docs.py new file mode 100644 index 00000000..50a7b1cb --- /dev/null +++ b/system/t12_api/docs.py @@ -0,0 +1,17 @@ +from api_lib import APITest + + +class TaskAPITestSwaggerDocs(APITest): + """ + GET /docs + """ + + def check(self): + resp = self.get("/docs/doc.json") + self.check_equal(resp.status_code, 200) + + resp = self.get("/docs/", allow_redirects=False) + self.check_equal(resp.status_code, 301) + + resp = self.get("/docs/index.html") + self.check_equal(resp.status_code, 200) diff --git a/system/t12_api/gpg.py b/system/t12_api/gpg.py index 8fb9a380..305a2b7e 100644 --- a/system/t12_api/gpg.py +++ b/system/t12_api/gpg.py @@ -2,32 +2,47 @@ import inspect import os import subprocess import tempfile +import json from api_lib import APITest def check_gpgkey_exists(gpg_key, keyring): - subprocess.check_call([ + p = subprocess.Popen([ "gpg", "--no-default-keyring", "--keyring", keyring, - "--fingerprint", gpg_key, - ]) + "--fingerprint", gpg_key], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + p.communicate() + if p.returncode != 0: + raise Exception("gpg key does not exists") class GPGAPITestAddKey(APITest): """ POST /gpg/key """ + requiresGPG2 = True + + fixtureCmds = [ + "gpgconf --kill dirmngr", + "gpgconf --launch dirmngr", + "sleep 2" + ] def check(self): with tempfile.NamedTemporaryFile(suffix=".pub") as keyring: gpgkeyid = "9E3E53F19C7DE460" resp = self.post("/api/gpg/key", json={ - "Keyserver": "keyserver.ubuntu.com", + "Keyserver": "hkp://keyserver.ubuntu.com:80", "Keyring": keyring.name, "GpgKeyID": gpgkeyid }) - + if resp.status_code != 200: + output = json.loads(resp.text) + print(f"{output}\n") self.check_equal(resp.status_code, 200) check_gpgkey_exists(gpgkeyid, keyring.name) diff --git a/system/t12_api/metrics.py b/system/t12_api/metrics.py index 829ac390..0b351c0b 100644 --- a/system/t12_api/metrics.py +++ b/system/t12_api/metrics.py @@ -7,6 +7,35 @@ class MetricsEnabledAPITest(APITest): """ def check(self): + d = "libboost-program-options-dev_1.62.0.1" + r = "foo" + f = "libboost-program-options-dev_1.62.0.1_i386.deb" + + self.check_equal(self.upload("/api/files/" + d, f).status_code, 200) + + self.check_equal(self.post("/api/repos", json={ + "Name": r, + "Comment": "test repo", + "DefaultDistribution": r, + "DefaultComponent": "main" + }).status_code, 201) + + self.check_equal(self.post(f"/api/repos/{r}/file/{d}").status_code, 200) + + self.check_equal(self.post("/api/publish/filesystem:apiandserve:", json={ + "SourceKind": "local", + "Sources": [ + { + "Component": "main", + "Name": r + } + ], + "Distribution": r, + "Signing": { + "Skip": True + } + }).status_code, 201) + resp = self.get("/api/metrics") self.check_equal(resp.status_code, 200) @@ -24,3 +53,18 @@ class MetricsEnabledAPITest(APITest): apiRequestsDurationSummary = "# TYPE aptly_api_http_request_duration_seconds summary" self.check_in(apiRequestsDurationSummary, resp.text) + + apiBuildInfoGauge = "# TYPE aptly_build_info gauge" + self.check_in(apiBuildInfoGauge, resp.text) + + apiFilesUploadedCounter = "# TYPE aptly_api_files_uploaded_total counter" + self.check_in(apiFilesUploadedCounter, resp.text) + + apiFilesUploadedCounterValue = "aptly_api_files_uploaded_total{directory=\"libboost-program-options-dev_1.62.0.1\"} 1" + self.check_in(apiFilesUploadedCounterValue, resp.text) + + apiReposPackageCountGauge = "# TYPE aptly_repos_package_count gauge" + self.check_in(apiReposPackageCountGauge, resp.text) + + apiReposPackageCountGaugeValue = "aptly_repos_package_count{component=\"main\",distribution=\"foo\",source=\"[foo:main]\"} 1" + self.check_in(apiReposPackageCountGaugeValue, resp.text) diff --git a/system/t12_api/mirrors.py b/system/t12_api/mirrors.py index 26960d68..6352b2ca 100644 --- a/system/t12_api/mirrors.py +++ b/system/t12_api/mirrors.py @@ -9,7 +9,7 @@ class MirrorsAPITestCreateShow(APITest): def check(self): mirror_name = self.random_name() mirror_desc = {'Name': mirror_name, - 'ArchiveURL': 'http://security.debian.org/debian-security/', + 'ArchiveURL': 'http://repo.aptly.info/system-tests/security.debian.org/debian-security/', 'Architectures': ['amd64'], 'Components': ['main'], 'Distribution': 'buster/updates'} @@ -27,7 +27,7 @@ class MirrorsAPITestCreateShow(APITest): resp = self.get("/api/mirrors/" + mirror_name) self.check_equal(resp.status_code, 200) self.check_subset({'Name': mirror_name, - 'ArchiveRoot': 'http://security.debian.org/debian-security/', + 'ArchiveRoot': 'http://repo.aptly.info/system-tests/security.debian.org/debian-security/', 'Architectures': ['amd64'], 'Components': ['main'], 'Distribution': 'buster/updates'}, resp.json()) @@ -43,8 +43,10 @@ class MirrorsAPITestCreateUpdate(APITest): def check(self): mirror_name = self.random_name() mirror_desc = {'Name': mirror_name, - 'ArchiveURL': 'https://packagecloud.io/varnishcache/varnish30/debian/', + 'ArchiveURL': 'http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/', 'Distribution': 'wheezy', + 'Keyrings': ["aptlytest.gpg"], + 'Architectures': ["amd64"], 'Components': ['main']} mirror_desc['IgnoreSignatures'] = True @@ -56,9 +58,9 @@ class MirrorsAPITestCreateUpdate(APITest): mirror_desc["Name"] = self.random_name() resp = self.put_task("/api/mirrors/" + mirror_name, json=mirror_desc) - self.check_equal(resp.json()["State"], 2) - + self.check_task(resp) _id = resp.json()['ID'] + resp = self.get("/api/tasks/" + str(_id) + "/detail") self.check_equal(resp.status_code, 200) self.check_equal(resp.json()['RemainingDownloadSize'], 0) @@ -67,7 +69,7 @@ class MirrorsAPITestCreateUpdate(APITest): resp = self.get("/api/mirrors/" + mirror_desc["Name"]) self.check_equal(resp.status_code, 200) self.check_subset({'Name': mirror_desc["Name"], - 'ArchiveRoot': 'https://packagecloud.io/varnishcache/varnish30/debian/', + 'ArchiveRoot': 'http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/', 'Distribution': 'wheezy'}, resp.json()) resp = self.get("/api/mirrors/" + mirror_desc["Name"] + "/packages") @@ -81,7 +83,7 @@ class MirrorsAPITestCreateDelete(APITest): def check(self): mirror_name = self.random_name() mirror_desc = {'Name': mirror_name, - 'ArchiveURL': 'https://packagecloud.io/varnishcache/varnish30/debian/', + 'ArchiveURL': 'http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/', 'IgnoreSignatures': True, 'Distribution': 'wheezy', 'Components': ['main']} @@ -90,7 +92,7 @@ class MirrorsAPITestCreateDelete(APITest): self.check_equal(resp.status_code, 201) resp = self.delete_task("/api/mirrors/" + mirror_name) - self.check_equal(resp.json()['State'], 2) + self.check_task(resp) class MirrorsAPITestCreateList(APITest): @@ -104,7 +106,7 @@ class MirrorsAPITestCreateList(APITest): mirror_name = self.random_name() mirror_desc = {'Name': mirror_name, - 'ArchiveURL': 'https://packagecloud.io/varnishcache/varnish30/debian/', + 'ArchiveURL': 'http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/', 'IgnoreSignatures': True, 'Distribution': 'wheezy', 'Components': ['main']} @@ -115,3 +117,38 @@ class MirrorsAPITestCreateList(APITest): resp = self.get("/api/mirrors") self.check_equal(resp.status_code, 200) self.check_equal(len(resp.json()), count + 1) + + +class MirrorsAPITestSkipArchitectureCheck(APITest): + """ + GET /api/mirrors, POST /api/mirrors, GET /api/mirrors + + This tests SkipArchitectureCheck and IgnoreSignatures via API. + The repo to be mirrored requires the SkipArchitectureCheck and SkipComponentCheck in order to be mirrored. + """ + def check(self): + resp = self.get("/api/mirrors") + self.check_equal(resp.status_code, 200) + count = len(resp.json()) + + mirror_name = self.random_name() + mirror_desc = {'Name': mirror_name, + 'ArchiveURL': 'http://repo.aptly.info/system-tests/pkg.duosecurity.com/Debian', + 'Architectures': ['amd64', 'i386'], + 'SkipArchitectureCheck': True, + 'SkipComponentCheck': True, + 'IgnoreSignatures': True, + 'Distribution': 'bookworm', + 'Components': ['main']} + + resp = self.post("/api/mirrors", json=mirror_desc) + self.check_equal(resp.status_code, 201) + + resp = self.get("/api/mirrors") + self.check_equal(resp.status_code, 200) + self.check_equal(len(resp.json()), count + 1) + + mirror_desc = {'Name': mirror_name, + 'IgnoreSignatures': True} + resp = self.put_task("/api/mirrors/" + mirror_name, json=mirror_desc) + self.check_task(resp) diff --git a/system/t12_api/packages.py b/system/t12_api/packages.py index fea11951..86cf6ea1 100644 --- a/system/t12_api/packages.py +++ b/system/t12_api/packages.py @@ -19,12 +19,10 @@ class PackagesAPITestShow(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) resp = self.post_task("/api/repos/" + repo_name + "/file/" + d) - self.check_equal(resp.json()['State'], 2) + self.check_task(resp) # get information about package - resp = self.get("/api/packages/" + urllib.parse.quote('Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e')) - self.check_equal(resp.status_code, 200) - self.check_equal(resp.json(), { + pyspi_json = { 'Architecture': 'any', 'Binary': 'python-at-spi', 'Build-Depends': 'debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev', # noqa @@ -41,7 +39,24 @@ class PackagesAPITestShow(APITest): 'ShortKey': 'Psource pyspi 0.6.1-1.3', 'Standards-Version': '3.7.3', 'Vcs-Svn': 'svn://svn.tribulaciones.org/srv/svn/pyspi/trunk', - 'Version': '0.6.1-1.3'}) + 'Version': '0.6.1-1.3' + } + + resp = self.get("/api/packages/" + urllib.parse.quote('Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e')) + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), pyspi_json) + + resp = self.get("/api/packages?q=pyspi") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), [pyspi_json["Key"]]) + + resp = self.get("/api/packages?q=pyspi&format=details") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), [pyspi_json]) resp = self.get("/api/packages/" + urllib.parse.quote('Pamd64 no-such-package 1.0 3a8b37cbd9a3559e')) self.check_equal(resp.status_code, 404) + + resp = self.get("/api/packages?q=no-such-package") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), []) diff --git a/system/t12_api/probes.py b/system/t12_api/probes.py new file mode 100644 index 00000000..3c248f14 --- /dev/null +++ b/system/t12_api/probes.py @@ -0,0 +1,27 @@ +from api_lib import APITest + + +class ReadyAPITest(APITest): + """ + GET /ready + """ + + def check(self): + resp = self.get("/api/ready") + self.check_equal(resp.status_code, 200) + + readyStatus = "{\"Status\":\"Aptly is ready\"}" + self.check_equal(readyStatus, resp.text) + + +class HealthyAPITest(APITest): + """ + GET /healthy + """ + + def check(self): + resp = self.get("/api/healthy") + self.check_equal(resp.status_code, 200) + + healthyStatus = "{\"Status\":\"Aptly is healthy\"}" + self.check_equal(healthyStatus, resp.text) diff --git a/system/t12_api/publish.py b/system/t12_api/publish.py index 99f339ed..82751579 100644 --- a/system/t12_api/publish.py +++ b/system/t12_api/publish.py @@ -1,11 +1,11 @@ -import os import inspect +import os +import threading -from api_lib import APITest +from api_lib import TASK_SUCCEEDED, APITest DefaultSigningOptions = { "Keyring": os.path.join(os.path.dirname(inspect.getsourcefile(APITest)), "files") + "/aptly.pub", - "SecretKeyring": os.path.join(os.path.dirname(inspect.getsourcefile(APITest)), "files") + "/aptly.sec", } @@ -26,11 +26,12 @@ class PublishAPITestRepo(APITest): "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) # publishing under prefix, default distribution prefix = self.random_name() - resp = self.post_task( + task = self.post_task( "/api/publish/" + prefix, json={ "SourceKind": "local", @@ -38,9 +39,11 @@ class PublishAPITestRepo(APITest): "Signing": DefaultSigningOptions, } ) + self.check_task(task) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'Origin': '', @@ -49,13 +52,12 @@ class PublishAPITestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'local', 'Sources': [{'Component': 'main', 'Name': repo_name}], 'Storage': '', 'Suite': ''} - self.check_equal(resp.json()['State'], 2) - all_repos = self.get("/api/publish") self.check_equal(all_repos.status_code, 200) self.check_in(repo_expected, all_repos.json()) @@ -72,7 +74,7 @@ class PublishAPITestRepo(APITest): # publishing under root, custom distribution, architectures distribution = self.random_name() - resp = self.post_task( + task = self.post_task( "/api/publish/:.", json={ "SourceKind": "local", @@ -82,10 +84,11 @@ class PublishAPITestRepo(APITest): "Architectures": ["i386", "amd64"], } ) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo2_expected = { 'AcquireByHash': False, 'Architectures': ['amd64', 'i386'], + 'Codename': '', 'Distribution': distribution, 'Label': '', 'Origin': '', @@ -94,6 +97,7 @@ class PublishAPITestRepo(APITest): 'Path': './' + distribution, 'Prefix': ".", 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'local', 'Sources': [{'Component': 'main', 'Name': repo_name}], 'Storage': '', @@ -120,6 +124,71 @@ class PublishAPITestRepo(APITest): self.check_in(repo2_expected, all_repos.json()) +class PublishAPITestRepoMultiDist(APITest): + """ + Test MultiDist publishing to subdirectory + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo_name, "DefaultDistribution": "bookworm"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) + + # publishing under prefix, default distribution + prefix = self.random_name() + task = self.post_task( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "MultiDist": True, + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + } + ) + self.check_task(task) + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'bookworm', + 'Label': '', + 'Origin': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'bookworm', + 'Prefix': prefix, + 'SkipContents': False, + 'MultiDist': True, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + self.check_exists("public/" + prefix + "/dists/bookworm/Release") + self.check_exists("public/" + prefix + + "/dists/bookworm/main/binary-i386/Packages") + self.check_exists("public/" + prefix + + "/dists/bookworm/main/Contents-i386.gz") + self.check_exists("public/" + prefix + + "/dists/bookworm/main/source/Sources") + self.check_exists( + "public/" + prefix + "/pool/bookworm/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + + class PublishSnapshotAPITest(APITest): """ POST /publish/:prefix (snapshots), GET /publish @@ -135,12 +204,14 @@ class PublishSnapshotAPITest(APITest): self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - self.check_equal(self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_task(task) prefix = self.random_name() - resp = self.post_task( + task = self.post_task( "/api/publish/" + prefix, json={ "AcquireByHash": True, @@ -154,9 +225,9 @@ class PublishSnapshotAPITest(APITest): "Label": "fun", } ) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) - _id = resp.json()['ID'] + _id = task.json()['ID'] resp = self.get("/api/tasks/" + str(_id) + "/detail") self.check_equal(resp.json()['RemainingNumberOfPackages'], 0) self.check_equal(resp.json()['TotalNumberOfPackages'], 1) @@ -164,9 +235,11 @@ class PublishSnapshotAPITest(APITest): repo_expected = { 'AcquireByHash': True, 'Architectures': ['i386'], + 'Codename': '', 'Distribution': 'squeeze', 'Label': 'fun', 'Origin': 'earth', + 'MultiDist': False, 'NotAutomatic': 'yes', 'ButAutomaticUpgrades': 'yes', 'Path': prefix + '/' + 'squeeze', @@ -209,10 +282,11 @@ class PublishUpdateAPITestRepo(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) prefix = self.random_name() - resp = self.post_task( + task = self.post_task( "/api/publish/" + prefix, json={ "Architectures": ["i386", "source"], @@ -221,8 +295,7 @@ class PublishUpdateAPITestRepo(APITest): "Signing": DefaultSigningOptions, } ) - - self.check_equal(resp.json()['State'], 2) + self.check_task(task) self.check_not_exists( "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") @@ -232,23 +305,26 @@ class PublishUpdateAPITestRepo(APITest): d = self.random_name() self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - self.check_equal(self.delete_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) # Update and switch AcquireByHash on. - resp = self.put_task( + task = self.put_task( "/api/publish/" + prefix + "/wheezy", json={ "AcquireByHash": True, "Signing": DefaultSigningOptions, } ) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo_expected = { 'AcquireByHash': True, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'Origin': '', @@ -257,6 +333,7 @@ class PublishUpdateAPITestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'local', 'Sources': [{'Component': 'main', 'Name': repo_name}], 'Storage': '', @@ -274,7 +351,200 @@ class PublishUpdateAPITestRepo(APITest): self.check_not_exists( "public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") - self.check_equal(self.delete_task("/api/publish/" + prefix + "/wheezy").json()['State'], 2) + task = self.delete_task("/api/publish/" + prefix + "/wheezy") + self.check_task(task) + self.check_not_exists("public/" + prefix + "dists/") + + +class PublishUpdateAPIMultiDist(APITest): + """ + Test MultiDist publishing to subdirectory + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo_name, "DefaultDistribution": "bookworm"}).status_code, 201) + + d = self.random_name() + self.check_equal( + self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) + + prefix = self.random_name() + task = self.post_task( + "/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + } + ) + self.check_task(task) + + self.check_not_exists( + "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_exists("public/" + prefix + + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) + + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) + + # Update and switch MultiDist on. + task = self.put_task( + "/api/publish/" + prefix + "/bookworm", + json={ + "MultiDist": True, + "Signing": DefaultSigningOptions, + } + ) + self.check_task(task) + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'bookworm', + 'Label': '', + 'Origin': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'bookworm', + 'Prefix': prefix, + 'SkipContents': False, + 'MultiDist': True, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + self.check_exists( + "public/" + prefix + "/pool/bookworm/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_not_exists( + "public/" + prefix + "/pool/bookworm/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + task = self.delete_task("/api/publish/" + prefix + "/bookworm") + self.check_task(task) + self.check_not_exists("public/" + prefix + "dists/") + + +class PublishConcurrentUpdateAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution (local repos), DELETE /publish/:prefix/:distribution + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal( + self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) + + prefix = self.random_name() + task = self.post_task( + "/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + } + ) + self.check_task(task) + + self.check_not_exists( + "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_exists("public/" + prefix + + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) + + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) + + def _do_update(result, index): + resp = self.put_task( + "/api/publish/" + prefix + "/wheezy", + json={ + "AcquireByHash": True, + "Signing": DefaultSigningOptions, + } + ) + try: + self.check_equal(resp.json()['State'], TASK_SUCCEEDED) + except BaseException as e: + result[index] = e + + n_workers = 10 + worker_results = [None] * n_workers + tasks = [threading.Thread(target=_do_update, args=(worker_results, i,)) for i in range(n_workers)] + [task.start() for task in tasks] + [task.join() for task in tasks] + for result in worker_results: + if isinstance(result, BaseException): + raise result + + repo_expected = { + 'AcquireByHash': True, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SkipContents': False, + 'MultiDist': False, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + self.check_exists("public/" + prefix + + "/dists/wheezy/main/binary-i386/by-hash") + + self.check_exists( + "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_not_exists( + "public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + task = self.delete_task("/api/publish/" + prefix + "/wheezy") + self.check_task(task) self.check_not_exists("public/" + prefix + "dists/") @@ -295,18 +565,18 @@ class PublishUpdateSkipCleanupAPITestRepo(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) prefix = self.random_name() - resp = self.post_task("/api/publish/" + prefix, + task = self.post_task("/api/publish/" + prefix, json={ "Architectures": ["i386", "source"], "SourceKind": "local", "Sources": [{"Name": repo_name}], "Signing": DefaultSigningOptions, }) - - self.check_equal(resp.json()['State'], 2) + self.check_task(task) self.check_not_exists( "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") @@ -315,7 +585,7 @@ class PublishUpdateSkipCleanupAPITestRepo(APITest): # Publish two repos, so that deleting one while skipping cleanup will # not delete the whole prefix. - resp = self.post_task("/api/publish/" + prefix, + task = self.post_task("/api/publish/" + prefix, json={ "Architectures": ["i386", "source"], "Distribution": "otherdist", @@ -323,26 +593,28 @@ class PublishUpdateSkipCleanupAPITestRepo(APITest): "Sources": [{"Name": repo_name}], "Signing": DefaultSigningOptions, }) - - self.check_equal(resp.json()['State'], 2) + self.check_task(task) d = self.random_name() self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - self.check_equal(self.delete_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) - resp = self.put_task("/api/publish/" + prefix + "/wheezy", + task = self.put_task("/api/publish/" + prefix + "/wheezy", json={ "Signing": DefaultSigningOptions, "SkipCleanup": True, }) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'Origin': '', @@ -351,6 +623,7 @@ class PublishUpdateSkipCleanupAPITestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'local', 'Sources': [{'Component': 'main', 'Name': repo_name}], 'Storage': '', @@ -365,7 +638,8 @@ class PublishUpdateSkipCleanupAPITestRepo(APITest): self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") - self.check_equal(self.delete_task("/api/publish/" + prefix + "/wheezy", params={"SkipCleanup": "1"}).json()['State'], 2) + task = self.delete_task("/api/publish/" + prefix + "/wheezy", params={"SkipCleanup": "1"}) + self.check_task(task) self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") @@ -387,13 +661,15 @@ class PublishSwitchAPITestRepo(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) snapshot1_name = self.random_name() - self.check_equal(self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot1_name}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot1_name}) + self.check_task(task) prefix = self.random_name() - resp = self.post_task( + task = self.post_task( "/api/publish/" + prefix, json={ "Architectures": ["i386", "source"], @@ -401,11 +677,12 @@ class PublishSwitchAPITestRepo(APITest): "Sources": [{"Name": snapshot1_name}], "Signing": DefaultSigningOptions, }) + self.check_task(task) - self.check_equal(resp.json()['State'], 2) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'NotAutomatic': '', @@ -414,6 +691,7 @@ class PublishSwitchAPITestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'snapshot', 'Sources': [{'Component': 'main', 'Name': snapshot1_name}], 'Storage': '', @@ -430,25 +708,29 @@ class PublishSwitchAPITestRepo(APITest): d = self.random_name() self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - self.check_equal(self.delete_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) snapshot2_name = self.random_name() - self.check_equal(self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot2_name}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot2_name}) + self.check_task(task) - resp = self.put_task( + task = self.put_task( "/api/publish/" + prefix + "/wheezy", json={ "Snapshots": [{"Component": "main", "Name": snapshot2_name}], "Signing": DefaultSigningOptions, "SkipContents": True, }) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'Origin': '', @@ -457,6 +739,7 @@ class PublishSwitchAPITestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': True, + 'MultiDist': False, 'SourceKind': 'snapshot', 'Sources': [{'Component': 'main', 'Name': snapshot2_name}], 'Storage': '', @@ -471,7 +754,8 @@ class PublishSwitchAPITestRepo(APITest): self.check_not_exists( "public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") - self.check_equal(self.delete_task("/api/publish/" + prefix + "/wheezy").json()['State'], 2) + task = self.delete_task("/api/publish/" + prefix + "/wheezy") + self.check_task(task) self.check_not_exists("public/" + prefix + "dists/") @@ -492,13 +776,15 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) snapshot1_name = self.random_name() - self.check_equal(self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot1_name}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot1_name}) + self.check_task(task) prefix = self.random_name() - resp = self.post_task("/api/publish/" + prefix, + task = self.post_task("/api/publish/" + prefix, json={ "Architectures": ["i386", "source"], "SourceKind": "snapshot", @@ -506,10 +792,11 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): "Signing": DefaultSigningOptions, }) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'NotAutomatic': '', @@ -518,6 +805,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'snapshot', 'Sources': [{'Component': 'main', 'Name': snapshot1_name}], 'Storage': '', @@ -533,7 +821,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): # Publish two snapshots, so that deleting one while skipping cleanup will # not delete the whole prefix. - resp = self.post_task("/api/publish/" + prefix, + task = self.post_task("/api/publish/" + prefix, json={ "Architectures": ["i386", "source"], "Distribution": "otherdist", @@ -542,10 +830,11 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): "Signing": DefaultSigningOptions, }) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'otherdist', 'Label': '', 'NotAutomatic': '', @@ -554,6 +843,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): 'Path': prefix + '/' + 'otherdist', 'Prefix': prefix, 'SkipContents': False, + 'MultiDist': False, 'SourceKind': 'snapshot', 'Sources': [{'Component': 'main', 'Name': snapshot1_name}], 'Storage': '', @@ -565,25 +855,29 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): d = self.random_name() self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - self.check_equal(self.delete_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) snapshot2_name = self.random_name() - self.check_equal(self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot2_name}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot2_name}) + self.check_task(task) - resp = self.put_task("/api/publish/" + prefix + "/wheezy", + task = self.put_task("/api/publish/" + prefix + "/wheezy", json={ "Snapshots": [{"Component": "main", "Name": snapshot2_name}], "Signing": DefaultSigningOptions, "SkipCleanup": True, "SkipContents": True, }) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) repo_expected = { 'AcquireByHash': False, 'Architectures': ['i386', 'source'], + 'Codename': '', 'Distribution': 'wheezy', 'Label': '', 'Origin': '', @@ -592,6 +886,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): 'Path': prefix + '/' + 'wheezy', 'Prefix': prefix, 'SkipContents': True, + 'MultiDist': False, 'SourceKind': 'snapshot', 'Sources': [{'Component': 'main', 'Name': snapshot2_name}], 'Storage': '', @@ -604,6 +899,661 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") - self.check_equal(self.delete_task("/api/publish/" + prefix + "/wheezy", params={"SkipCleanup": "1"}).json()['State'], 2) + task = self.delete_task("/api/publish/" + prefix + "/wheezy", params={"SkipCleanup": "1"}) + self.check_task(task) self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + +class PublishShowAPITestRepo(APITest): + """ + GET /publish/:prefix/:distribution + """ + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal( + self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + self.check_equal(self.post_task("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Origin': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SkipContents': False, + 'MultiDist': False, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo1_name}], + 'Storage': '', + 'Suite': ''} + repo = self.get("/api/publish/" + prefix + "/wheezy") + self.check_equal(repo.status_code, 200) + self.check_equal(repo_expected, repo.json()) + + +class ServePublishedListTestRepo(APITest): + """ + GET /repos + """ + + def check(self): + d = "libboost-program-options-dev_1.62.0.1" + r = "bar" + f = "libboost-program-options-dev_1.62.0.1_i386.deb" + + self.check_equal(self.upload("/api/files/" + d, f).status_code, 200) + + self.check_equal(self.post("/api/repos", json={ + "Name": r, + "Comment": "test repo", + "DefaultDistribution": r, + "DefaultComponent": "main" + }).status_code, 201) + + self.check_equal(self.post(f"/api/repos/{r}/file/{d}").status_code, 200) + + self.check_equal(self.post("/api/publish/filesystem:apiandserve:", json={ + "SourceKind": "local", + "Sources": [ + { + "Component": "main", + "Name": r + } + ], + "Distribution": r, + "Signing": { + "Skip": True + } + }).status_code, 201) + + get = self.get("/repos") + expected_content_type = "text/html; charset=utf-8" + if get.headers['content-type'] != expected_content_type: + raise Exception(f"Received content-type {get.headers['content-type']} was not: {expected_content_type}") + + excepted_content = b'
\napiandserve\n
' + if excepted_content != get.content: + raise Exception(f"Expected content {excepted_content} was not: {get.content}") + + +class ServePublishedTestRepo(APITest): + """ + GET /repos/:storage/*pkgPath + """ + + def check(self): + d = self.random_name() + r = self.random_name() + f = "libboost-program-options-dev_1.62.0.1_i386.deb" + + self.check_equal(self.upload("/api/files/" + d, f).status_code, 200) + + self.check_equal(self.post("/api/repos", json={ + "Name": r, + "Comment": "test repo", + "DefaultDistribution": r, + "DefaultComponent": "main" + }).status_code, 201) + + self.check_equal(self.post(f"/api/repos/{r}/file/{d}").status_code, 200) + + self.check_equal(self.post("/api/publish/filesystem:apiandserve:", json={ + "SourceKind": "local", + "Sources": [ + { + "Component": "main", + "Name": r + } + ], + "Distribution": r, + "Signing": { + "Skip": True + } + }).status_code, 201) + + get = self.get(f"/repos/apiandserve/pool/main/b/boost-defaults/{f}") + deb_content_types = [ + "application/x-deb", + "application/x-debian-package", + "application/vnd.debian.binary-package" + ] + if get.headers['content-type'] not in deb_content_types: + raise Exception(f"Received content-type {get.headers['content-type']} not one of expected: {deb_content_types}") + + if len(get.content) != 3428: + raise Exception(f"Expected file size 3428 bytes != {len(get.content)} bytes") + + +class ServePublishedNotFoundTestRepo(APITest): + """ + GET /repos/:storage/*pkgPath + """ + + def check(self): + d = self.random_name() + r = self.random_name() + f = "libboost-program-options-dev_1.62.0.1_i386.deb" + + self.check_equal(self.upload("/api/files/" + d, f).status_code, 200) + + self.check_equal(self.post("/api/repos", json={ + "Name": r, + "Comment": "test repo", + "DefaultDistribution": r, + "DefaultComponent": "main" + }).status_code, 201) + + self.check_equal(self.post(f"/api/repos/{r}/file/{d}").status_code, 200) + + self.check_equal(self.post("/api/publish/filesystem:apiandserve:", json={ + "SourceKind": "local", + "Sources": [ + { + "Component": "main", + "Name": r + } + ], + "Distribution": r, + "Signing": { + "Skip": True + } + }).status_code, 201) + + get = self.get("/repos/apiandserve/pool/main/b/boost-defaults/i-dont-exist") + if get.status_code != 404: + raise Exception(f"Expected status 404 != {get.status_code}") + + +class PublishSourcesAddAPITestRepo(APITest): + """ + POST /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # Actual test + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/sources", + json={"Component": "test", "Name": repo2_name} + ).status_code, 201) + + sources_expected = [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourceUpdateAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution/sources/main + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # Actual test + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/main", + json={"Component": "main", "Name": repo2_name} + ).status_code, 200) + + sources_expected = [{"Component": "main", "Name": repo2_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourcesUpdateAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources", + json=[{"Component": "test", "Name": repo1_name}, {"Component": "other-test", "Name": repo2_name}] + ).status_code, 200) + + sources_expected = [{"Component": "other-test", "Name": repo2_name}, {"Component": "test", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourceRemoveAPITestRepo(APITest): + """ + DELETE /publish/:prefix/:distribution/sources/test + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/test").status_code, 200) + + sources_expected = [{"Component": "main", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourcesDropAPITestRepo(APITest): + """ + DELETE /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/test").status_code, 200) + + # Actual test + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources").status_code, 200) + + self.check_equal(self.get("/api/publish/" + prefix + "/wheezy/sources").status_code, 404) + + +class PublishSourcesListAPITestRepo(APITest): + """ + GET /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/sources", + json={"Component": "test", "Name": repo1_name} + ).status_code, 201) + + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/main", + json={"Component": "main", "Name": repo2_name} + ).status_code, 200) + + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/main").status_code, 200) + + sources_expected = [{"Component": "test", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourceReplaceAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution/sources/main + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/main", + json={"Component": "test", "Name": repo2_name} + ).status_code, 200) + + sources_expected = [{"Component": "test", "Name": repo2_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishUpdateSourcesAPITestRepo(APITest): + """ + POST /publish/:prefix/:distribution/update + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + repo3_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo3_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo3_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "Signing": DefaultSigningOptions, + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}], + } + ).status_code, 201) + + # remove 'main' component + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/main").status_code, 200) + + # update 'test' component + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/test", + json={"Component": "test", "Name": repo1_name} + ).status_code, 200) + + # add 'other-test' component + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/sources", + json={"Component": "other-test", "Name": repo3_name} + ).status_code, 201) + + sources_expected = [{"Component": "other-test", "Name": repo3_name}, {"Component": "test", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + # update published repository and publish new content + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/update", + json={ + "AcquireByHash": True, + "MultiDist": False, + "Signing": DefaultSigningOptions, + "SkipBz2": True, + "SkipContents": True, + } + ).status_code, 200) + + repo_expected = { + 'AcquireByHash': True, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SkipContents': True, + 'MultiDist': False, + 'SourceKind': 'local', + 'Sources': [{"Component": "other-test", "Name": repo3_name}, {"Component": "test", "Name": repo1_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) diff --git a/system/t12_api/repos.py b/system/t12_api/repos.py index 46ab03dd..424f9f49 100644 --- a/system/t12_api/repos.py +++ b/system/t12_api/repos.py @@ -1,4 +1,5 @@ from api_lib import APITest + from .publish import DefaultSigningOptions @@ -27,6 +28,51 @@ class ReposAPITestCreateShow(APITest): self.check_equal(self.get("/api/repos/" + self.random_name()).status_code, 404) +class ReposAPITestCreateFromSnapshot(APITest): + """ + Create repo from snapshot + """ + def check(self): + initial_repo = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": initial_repo}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + task = self.post_task("/api/repos/" + initial_repo + "/file/" + d) + self.check_task(task) + + snapshot_name = self.random_name() + task = self.post_task("/api/repos/" + initial_repo + '/snapshots', json={'Name': snapshot_name}) + self.check_task(task) + self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 200) + + repo_from_snapshot = self.random_name() + new_repo = {'Name': repo_from_snapshot, + 'FromSnapshot': snapshot_name} + + resp = self.post("/api/repos", json=new_repo) + self.check_equal(resp.status_code, 201) + + self.check_equal(self.get("/api/repos/" + repo_from_snapshot + "/packages").json(), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']) + + +class ReposAPITestCreateFromWrongSnapshot(APITest): + """ + Create repo from snapshot + """ + def check(self): + snapshot_name = self.random_name() # non-existing snapshot + repo_from_snapshot = self.random_name() + new_repo = {'Name': repo_from_snapshot, + 'FromSnapshot': snapshot_name} + + resp = self.post("/api/repos", json=new_repo) + self.check_equal(resp.status_code, 404) + + class ReposAPITestCreateIndexDelete(APITest): """ GET /api/repos, POST /api/repos, DELETE /api/repos/:name @@ -40,7 +86,8 @@ class ReposAPITestCreateIndexDelete(APITest): names = [repo["Name"] for repo in repos] assert repo_name in names - self.check_equal(self.delete_task("/api/repos/" + repo_name).json()['State'], 2) + resp = self.delete_task("/api/repos/" + repo_name) + self.check_task(resp) self.check_equal(self.delete("/api/repos/" + repo_name).status_code, 404) self.check_equal(self.get("/api/repos/" + repo_name).status_code, 404) @@ -60,25 +107,32 @@ class ReposAPITestCreateIndexDelete(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) resp = self.post_task("/api/repos/" + repo_name + "/file/" + d) - self.check_equal(resp.json()['State'], 2) + self.check_task(resp) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/snapshots", json={"Name": repo_name}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/snapshots", json={"Name": repo_name}) + self.check_task(task) - self.check_equal(self.post_task("/api/publish", - json={ - "SourceKind": "local", - "Sources": [{"Name": repo_name}], - "Signing": DefaultSigningOptions, - }).json()['State'], 2) + resp = self.post_task("/api/publish", + json={ + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + }) + self.check_task(resp) # repo is not deletable while it is published - self.check_equal(self.delete_task("/api/repos/" + repo_name).json()['State'], 3) - self.check_equal(self.delete_task("/api/repos/" + repo_name, params={"force": "1"}).json()['State'], 3) + task = self.delete_task("/api/repos/" + repo_name) + self.check_task_fail(task) + task = self.delete_task("/api/repos/" + repo_name, params={"force": "1"}) + self.check_task_fail(task) # drop published - self.check_equal(self.delete_task("/api/publish//" + distribution).json()['State'], 2) - self.check_equal(self.delete_task("/api/repos/" + repo_name).json()['State'], 3) - self.check_equal(self.delete_task("/api/repos/" + repo_name, params={"force": "1"}).json()['State'], 2) + task = self.delete_task("/api/publish//" + distribution) + self.check_task(task) + task = self.delete_task("/api/repos/" + repo_name) + self.check_task_fail(task) + task = self.delete_task("/api/repos/" + repo_name, params={"force": "1"}) + self.check_task(task) self.check_equal(self.get("/api/repos/" + repo_name).status_code, 404) @@ -96,7 +150,7 @@ class ReposAPITestAdd(APITest): "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) resp = self.post_task("/api/repos/" + repo_name + "/file/" + d) - self.check_equal(resp.json()['State'], 2) + self.check_task(resp) resp = self.get("/api/tasks/" + str(resp.json()['ID']) + "/output") self.check_equal(resp.status_code, 200) @@ -124,7 +178,8 @@ class ReposAPITestAddNotFullRemove(APITest): self.check_equal(self.upload("/api/files/" + d, "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "aptly.pub").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) self.check_equal(self.get("/api/repos/" + repo_name + "/packages").json(), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) self.check_exists("upload/" + d + "/aptly.pub") @@ -144,7 +199,8 @@ class ReposAPITestAddNoRemove(APITest): self.check_equal(self.upload("/api/files/" + d, "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d, params={"noRemove": 1}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d, params={"noRemove": 1}) + self.check_task(task) self.check_equal(self.get("/api/repos/" + repo_name + "/packages").json(), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) self.check_exists("upload/" + d + "/pyspi_0.6.1-1.3.dsc") @@ -164,7 +220,7 @@ class ReposAPITestAddFile(APITest): "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) resp = self.post_task("/api/repos/" + repo_name + "/file/" + d + "/libboost-program-options-dev_1.49.0.1_i386.deb") - self.check_equal(resp.json()['State'], 2) + self.check_task(resp) resp = self.get("/api/tasks/" + str(resp.json()['ID']) + "/output") self.check_equal(resp.status_code, 200) @@ -196,7 +252,7 @@ class ReposAPITestInclude(APITest): self.check_equal(resp.status_code, 200) resp = self.post_task("/api/repos/" + repo_name + "/include/" + d, params={"ignoreSignature": 1}) - self.check_equal(resp.json()['State'], 2) + self.check_task(resp) resp = self.get("/api/tasks/" + str(resp.json()['ID']) + "/output") self.check_equal(resp.status_code, 200) @@ -224,7 +280,8 @@ class ReposAPITestShowQuery(APITest): "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages", params={"q": "pyspi"}).json()), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) @@ -257,13 +314,15 @@ class ReposAPITestAddMultiple(APITest): "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d + "/pyspi_0.6.1-1.3.dsc", - params={"noRemove": 1}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d + "/pyspi_0.6.1-1.3.dsc", + params={"noRemove": 1}) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d + "/pyspi-0.6.1-1.3.stripped.dsc").json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d + "/pyspi-0.6.1-1.3.stripped.dsc") + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) @@ -285,34 +344,39 @@ class ReposAPITestPackagesAddDelete(APITest): "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89', - 'Psource no-such-package 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 3) + task = self.post_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89', + 'Psource no-such-package 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task_fail(task) - self.check_equal(self.delete_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.delete_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', @@ -323,10 +387,77 @@ class ReposAPITestPackagesAddDelete(APITest): self.check_equal(self.post("/api/repos", json={"Name": repo_name2, "Comment": "fun repo"}).status_code, 201) - self.check_equal(self.post_task("/api/repos/" + repo_name2 + "/packages/", - json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89', - 'Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']}).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name2 + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89', + 'Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']}) + self.check_task(task) self.check_equal(sorted(self.get("/api/repos/" + repo_name2 + "/packages").json()), ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + +class ReposAPITestShowMaxVersion(APITest): + """ + POST /api/repos/:name/file/:dir, GET /api/repos/:name/packages + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", + "libboost-program-options-dev_1.62.0.1_i386.deb" + ).status_code, 200) + + resp = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(resp) + + resp = self.get("/api/tasks/" + str(resp.json()['ID']) + "/output") + self.check_equal(resp.status_code, 200) + + self.check_in(b"Added: libboost-program-options-dev_1.49.0.1_i386 added, libboost-program-options-dev_1.62.0.1_i386 added", resp.content) + self.check_not_in(b"Removed: ", resp.content) + self.check_not_in(b"Failed files: ", resp.content) + self.check_not_in(b"Warnings: ", resp.content) + + self.check_equal(self.get("/api/repos/" + repo_name + "/packages?maximumVersion=1").json(), ['Pi386 libboost-program-options-dev 1.62.0.1 7760e62f99c551cb']) + + +class ReposAPITestCopyPackage(APITest): + """ + POST /api/repos/:name/copy/:src/:file + """ + def check(self): + pkg_name = "libboost-program-options-dev_1.49.0.1_i386" + + # Creating origin repo + repo1_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo1_name, "Comment": "origin repo"}).status_code, 201) + + # Uploading test package + d = self.random_name() + self.check_equal(self.upload(f"/api/files/{d}", f"{pkg_name}.deb").status_code, 200) + resp = self.post_task(f"/api/repos/{repo1_name}/file/{d}") + self.check_task(resp) + + # Creating target repo + repo2_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo2_name, "Comment": "target repo"}).status_code, 201) + + # Copy the package + resp = self.post_task(f"/api/repos/{repo2_name}/copy/{repo1_name}/{pkg_name}") + self.check_task(resp) + + # Test bad query + resp = self.post_task(f"/api/repos/{repo2_name}/copy/{repo1_name}/lalala%20%3E%3E") + self.check_task_fail(resp, expected_output="Task failed with error: unable to parse query 'lalala >>': parsing failed: unexpected token >>: expecting end of query") + + # Test non-existing package + resp = self.post_task(f"/api/repos/{repo2_name}/copy/{repo1_name}/lalala") + self.check_task_fail(resp, expected_output="Task failed with error: no package found for filter: 'lalala'") + + self.check_equal(self.get(f"/api/repos/{repo2_name}/packages").json(), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']) diff --git a/system/t12_api/snapshots.py b/system/t12_api/snapshots.py index 17e67bcf..fb6ba66f 100644 --- a/system/t12_api/snapshots.py +++ b/system/t12_api/snapshots.py @@ -1,4 +1,5 @@ from api_lib import APITest + from .publish import DefaultSigningOptions @@ -6,14 +7,15 @@ class SnapshotsAPITestCreateShowEmpty(APITest): """ GET /api/snapshots/:name, POST /api/snapshots, GET /api/snapshots/:name/packages """ + def check(self): snapshot_name = self.random_name() snapshot_desc = {'Description': 'fun snapshot', 'Name': snapshot_name} # create empty snapshot - resp = self.post_task("/api/snapshots", json=snapshot_desc) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/snapshots", json=snapshot_desc) + self.check_task(task) self.check_subset(snapshot_desc, self.get("/api/snapshots/" + snapshot_name).json()) self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 200) @@ -25,8 +27,8 @@ class SnapshotsAPITestCreateShowEmpty(APITest): self.check_equal(self.get("/api/snapshots/" + self.random_name()).status_code, 404) # create snapshot with duplicate name - resp = self.post_task("/api/snapshots", json=snapshot_desc) - self.check_equal(resp.json()['State'], 3) + task = self.post_task("/api/snapshots", json=snapshot_desc) + self.check_task_fail(task) class SnapshotsAPITestCreateFromRefs(APITest): @@ -34,6 +36,7 @@ class SnapshotsAPITestCreateFromRefs(APITest): GET /api/snapshots/:name, POST /api/snapshots, GET /api/snapshots/:name/packages, GET /api/snapshots """ + def check(self): snapshot_name = self.random_name() snapshot_desc = {'Description': 'fun snapshot', @@ -46,8 +49,8 @@ class SnapshotsAPITestCreateFromRefs(APITest): # create empty snapshot empty_snapshot_name = self.random_name() - resp = self.post_task("/api/snapshots", json={"Name": empty_snapshot_name}) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/snapshots", json={"Name": empty_snapshot_name}) + self.check_task(task) self.check_equal( self.get("/api/snapshots/" + empty_snapshot_name).json()['Description'], "Created as empty" ) @@ -58,14 +61,15 @@ class SnapshotsAPITestCreateFromRefs(APITest): d = self.random_name() self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) # create snapshot with empty snapshot as source and package snapshot = snapshot_desc.copy() snapshot['PackageRefs'] = ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"] snapshot['SourceSnapshots'] = [empty_snapshot_name] - resp = self.post_task("/api/snapshots", json=snapshot) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/snapshots", json=snapshot) + self.check_task(task) snapshot.pop('SourceSnapshots') snapshot.pop('PackageRefs') resp = self.get("/api/snapshots/" + snapshot_name) @@ -78,10 +82,10 @@ class SnapshotsAPITestCreateFromRefs(APITest): self.check_equal(resp.json(), ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"]) # create snapshot with unreferenced package - resp = self.post_task("/api/snapshots", json={ + task = self.post_task("/api/snapshots", json={ "Name": self.random_name(), "PackageRefs": ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378", "Pamd64 no-such-package 1.2 91"]}) - self.check_equal(resp.json()['State'], 3) + self.check_task_fail(task) # list snapshots resp = self.get("/api/snapshots", params={"sort": "time"}) @@ -94,13 +98,14 @@ class SnapshotsAPITestCreateFromRepo(APITest): """ POST /api/repos, POST /api/repos/:name/snapshots, GET /api/snapshots/:name """ + def check(self): repo_name = self.random_name() snapshot_name = self.random_name() self.check_equal(self.post("/api/repos", json={"Name": repo_name}).status_code, 201) - resp = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_task(task) self.check_equal([], self.get("/api/snapshots/" + snapshot_name + "/packages", params={"format": "details"}).json()) @@ -109,10 +114,11 @@ class SnapshotsAPITestCreateFromRepo(APITest): self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - resp = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_task(task) self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 200) self.check_subset({'Architecture': 'i386', @@ -129,26 +135,27 @@ class SnapshotsAPITestCreateFromRepo(APITest): params={"format": "details", "q": "Version (> 0.6.1-1.4)"}).json()[0]) # duplicate snapshot name - resp = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) - self.check_equal(resp.json()['State'], 3) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_task_fail(task) class SnapshotsAPITestCreateUpdate(APITest): """ POST /api/snapshots, PUT /api/snapshots/:name, GET /api/snapshots/:name """ + def check(self): snapshot_name = self.random_name() snapshot_desc = {'Description': 'fun snapshot', 'Name': snapshot_name} - resp = self.post_task("/api/snapshots", json=snapshot_desc) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/snapshots", json=snapshot_desc) + self.check_task(task) new_snapshot_name = self.random_name() - resp = self.put_task("/api/snapshots/" + snapshot_name, json={'Name': new_snapshot_name, + task = self.put_task("/api/snapshots/" + snapshot_name, json={'Name': new_snapshot_name, 'Description': 'New description'}) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) resp = self.get("/api/snapshots/" + new_snapshot_name) self.check_equal(resp.status_code, 200) @@ -156,9 +163,9 @@ class SnapshotsAPITestCreateUpdate(APITest): "Description": "New description"}, resp.json()) # duplicate name - resp = self.put_task("/api/snapshots/" + new_snapshot_name, json={'Name': new_snapshot_name, + task = self.put_task("/api/snapshots/" + new_snapshot_name, json={'Name': new_snapshot_name, 'Description': 'New description'}) - self.check_equal(resp.json()['State'], 3) + self.check_task_fail(task) # missing snapshot resp = self.put("/api/snapshots/" + snapshot_name, json={}) @@ -169,35 +176,40 @@ class SnapshotsAPITestCreateDelete(APITest): """ POST /api/snapshots, DELETE /api/snapshots/:name, GET /api/snapshots/:name """ + def check(self): snapshot_name = self.random_name() snapshot_desc = {'Description': 'fun snapshot', 'Name': snapshot_name} # deleting unreferenced snapshot - resp = self.post_task("/api/snapshots", json=snapshot_desc) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/snapshots", json=snapshot_desc) + self.check_task(task) - self.check_equal(self.delete_task("/api/snapshots/" + snapshot_name).json()['State'], 2) + task = self.delete_task("/api/snapshots/" + snapshot_name) + self.check_task(task) self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 404) # deleting referenced snapshot snap1, snap2 = self.random_name(), self.random_name() - self.check_equal(self.post_task("/api/snapshots", json={"Name": snap1}).json()['State'], 2) + task = self.post_task("/api/snapshots", json={"Name": snap1}) + self.check_task(task) self.check_equal( self.post_task( "/api/snapshots", json={"Name": snap2, "SourceSnapshots": [snap1]} ).json()['State'], 2 ) - self.check_equal(self.delete_task("/api/snapshots/" + snap1).json()['State'], 3) + task = self.delete_task("/api/snapshots/" + snap1) + self.check_task_fail(task) self.check_equal(self.get("/api/snapshots/" + snap1).status_code, 200) - self.check_equal(self.delete_task("/api/snapshots/" + snap1, params={"force": "1"}).json()['State'], 2) + task = self.delete_task("/api/snapshots/" + snap1, params={"force": "1"}) + self.check_task(task) self.check_equal(self.get("/api/snapshots/" + snap1).status_code, 404) # deleting published snapshot - resp = self.post_task( + task = self.post_task( "/api/publish", json={ "SourceKind": "snapshot", @@ -207,16 +219,19 @@ class SnapshotsAPITestCreateDelete(APITest): "Signing": DefaultSigningOptions, } ) - self.check_equal(resp.json()['State'], 2) + self.check_task(task) - self.check_equal(self.delete_task("/api/snapshots/" + snap2).json()['State'], 3) - self.check_equal(self.delete_task("/api/snapshots/" + snap2, params={"force": "1"}).json()['State'], 3) + task = self.delete_task("/api/snapshots/" + snap2) + self.check_task_fail(task) + task = self.delete_task("/api/snapshots/" + snap2, params={"force": "1"}) + self.check_task_fail(task) class SnapshotsAPITestSearch(APITest): """ POST /api/snapshots, GET /api/snapshots?sort=name, GET /api/snapshots/:name """ + def check(self): repo_name = self.random_name() @@ -227,10 +242,11 @@ class SnapshotsAPITestSearch(APITest): self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) - resp = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_task(task) resp = self.get("/api/snapshots/" + snapshot_name + "/packages", params={"q": "libboost-program-options-dev", "format": "details"}) @@ -250,6 +266,7 @@ class SnapshotsAPITestDiff(APITest): """ GET /api/snapshot/:name/diff/:name2 """ + def check(self): repos = [self.random_name() for x in range(2)] snapshots = [self.random_name() for x in range(2)] @@ -261,13 +278,14 @@ class SnapshotsAPITestDiff(APITest): self.check_equal(self.upload("/api/files/" + d, "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) - self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], 2) + task = self.post_task("/api/repos/" + repos[-1] + "/file/" + d) + self.check_task(task) - resp = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshots[0]}) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/repos/" + repos[-1] + '/snapshots', json={'Name': snapshots[0]}) + self.check_task(task) - resp = self.post_task("/api/snapshots", json={'Name': snapshots[1]}) - self.check_equal(resp.json()['State'], 2) + task = self.post_task("/api/snapshots", json={'Name': snapshots[1]}) + self.check_task(task) resp = self.get("/api/snapshots/" + snapshots[0] + "/diff/" + snapshots[1]) self.check_equal(resp.status_code, 200) @@ -286,3 +304,250 @@ class SnapshotsAPITestDiff(APITest): resp = self.get("/api/snapshots/" + snapshots[1] + "/diff/" + snapshots[1]) self.check_equal(resp.status_code, 200) self.check_equal(resp.json(), []) + + +class SnapshotsAPITestMerge(APITest): + """ + POST /api/snapshots, POST /api/snapshots/:name/merge, GET /api/snapshots/:name, DELETE /api/snapshots/:name + """ + + def check(self): + sources = [ + {"Description": "fun snapshot", "Name": self.random_name()} + for _ in range(2) + ] + + # create source snapshots + for source in sources: + task = self.post_task("/api/snapshots", json=source) + self.check_task(task) + + # create merge snapshot + merged_name = self.random_name() + task = self.post_task( + f"/api/snapshots/{merged_name}/merge", + json={ + "Sources": [source["Name"] for source in sources], + }, + ) + self.check_task(task) + + # check merge snapshot + resp = self.get(f"/api/snapshots/{merged_name}") + self.check_equal(resp.status_code, 200) + source_list = ", ".join(f"'{source['Name']}'" for source in sources) + self.check_subset( + { + "Name": merged_name, + "Description": f"Merged from sources: {source_list}", + }, + resp.json(), + ) + + # remove merge snapshot + task = self.delete_task(f"/api/snapshots/{merged_name}") + self.check_task(task) + + # create merge snapshot without sources + merged_name = self.random_name() + resp = self.post( + f"/api/snapshots/{merged_name}/merge", json={"Sources": []} + ) + self.check_equal(resp.status_code, 400) + self.check_equal( + resp.json()["error"], "At least one source snapshot is required" + ) + self.check_equal(self.get(f"/api/snapshots/{merged_name}").status_code, 404) + + # create merge snapshot with non-existing source + merged_name = self.random_name() + non_existing_source = self.random_name() + resp = self.post( + f"/api/snapshots/{merged_name}/merge", + json={"Sources": [non_existing_source]}, + ) + self.check_equal( + resp.json()["error"], f"snapshot with name {non_existing_source} not found" + ) + self.check_equal(resp.status_code, 404) + + self.check_equal(self.get(f"/api/snapshots/{merged_name}").status_code, 404) + + # create merge snapshot with used name + merged_name = sources[0]["Name"] + resp = self.post( + f"/api/snapshots/{merged_name}/merge", + json={"Sources": [source["Name"] for source in sources]}, + ) + self.check_equal( + resp.json()["error"], + f"unable to create snapshot: snapshot with name {sources[0]['Name']} already exists", + ) + self.check_equal(resp.status_code, 500) + + # create merge snapshot with "latest" and "no-remove" flags (should fail) + merged_name = self.random_name() + resp = self.post( + f"/api/snapshots/{merged_name}/merge", + json={ + "Sources": [source["Name"] for source in sources], + }, + params={"latest": "1", "no-remove": "1"}, + ) + self.check_equal( + resp.json()["error"], "no-remove and latest are mutually exclusive" + ) + self.check_equal(resp.status_code, 400) + + +class SnapshotsAPITestPull(APITest): + """ + POST /api/snapshots/:name/pull, POST /api/snapshots, GET /api/snapshots/:name/packages?name=:package_name + """ + + def check(self): + repo_with_libboost = self.random_name() + empty_repo = self.random_name() + snapshot_repo_with_libboost = self.random_name() + snapshot_empty_repo = self.random_name() + + # create repo with file in it and snapshot of it + self.check_equal(self.post("/api/repos", json={"Name": repo_with_libboost}).status_code, 201) + + dir_name = self.random_name() + self.check_equal(self.upload(f"/api/files/{dir_name}", + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + self.check_equal(self.post(f"/api/repos/{repo_with_libboost}/file/{dir_name}").status_code, 200) + + resp = self.post(f"/api/repos/{repo_with_libboost}/snapshots", json={'Name': snapshot_repo_with_libboost}) + self.check_equal(resp.status_code, 201) + + # create empty repo and snapshot of it + self.check_equal(self.post("/api/repos", json={"Name": empty_repo}).status_code, 201) + + resp = self.post(f"/api/repos/{empty_repo}/snapshots", json={'Name': snapshot_empty_repo}) + self.check_equal(resp.status_code, 201) + + # pull libboost from repo_with_libboost to empty_repo, save into snapshot_pull_libboost + snapshot_pull_libboost = self.random_name() + + # dry run first + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={ + 'Source': snapshot_repo_with_libboost, + 'Destination': snapshot_pull_libboost, + 'Queries': [ + 'libboost-program-options-dev' + ], + 'Architectures': [ + 'amd64' + 'i386' + ] + }) + self.check_equal(resp.status_code, 200) + + # dry run, all-matches + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1&all-matches=1", json={ + 'Source': snapshot_repo_with_libboost, + 'Destination': snapshot_pull_libboost, + 'Queries': [ + 'libboost-program-options-dev' + ], + 'Architectures': [ + 'amd64' + 'i386' + ] + }) + self.check_equal(resp.status_code, 200) + + # missing argument + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={ + 'Source': snapshot_repo_with_libboost, + 'Destination': snapshot_pull_libboost, + }) + self.check_equal(resp.status_code, 400) + + # dry run, emtpy architectures + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={ + 'Source': snapshot_repo_with_libboost, + 'Destination': snapshot_pull_libboost, + 'Queries': [ + 'libboost-program-options-dev' + ], + 'Architectures': [] + }) + self.check_equal(resp.status_code, 500) + + # dry run, non-existing To + resp = self.post("/api/snapshots/asd123/pull", json={ + 'Source': snapshot_repo_with_libboost, + 'Destination': snapshot_pull_libboost, + 'Queries': [ + 'libboost-program-options-dev' + ] + }) + self.check_equal(resp.status_code, 404) + + # dry run, non-existing source + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={ + 'Source': "asd123", + 'Destination': snapshot_pull_libboost, + 'Queries': [ + 'libboost-program-options-dev' + ] + }) + self.check_equal(resp.status_code, 404) + + # snapshot pull + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={ + 'Source': snapshot_repo_with_libboost, + 'Destination': snapshot_pull_libboost, + 'Queries': [ + 'libboost-program-options-dev' + ], + 'Architectures': [ + 'amd64' + 'i386' + ] + }) + self.check_equal(resp.status_code, 201) + self.check_subset({ + 'Name': snapshot_pull_libboost, + 'SourceKind': 'snapshot', + 'Description': f"Pulled into '{snapshot_empty_repo}' with '{snapshot_repo_with_libboost}' as source, pull request was: 'libboost-program-options-dev'", + }, resp.json()) + + # check that snapshot_pull_libboost contains libboost + resp = self.get(f"/api/snapshots/{snapshot_pull_libboost}/packages?name=libboost-program-options-dev") + self.check_equal(resp.status_code, 200) + + # pull from non-existing source + non_existing_source = self.random_name() + destination = self.random_name() + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={ + 'Source': non_existing_source, + 'Destination': destination, + 'Queries': [ + 'Name (~ *)' + ], + 'Architectures': [ + 'all', + ] + }) + self.check_equal(resp.status_code, 404) + self.check_equal(resp.json()['error'], f"snapshot with name {non_existing_source} not found") + + # pull to non-existing snapshot + non_existing_snapshot = self.random_name() + destination = self.random_name() + resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={ + 'Source': non_existing_snapshot, + 'Destination': destination, + 'Queries': [ + 'Name (~ *)' + ], + 'Architectures': [ + 'all', + ] + }) + self.check_equal(resp.status_code, 404) + self.check_equal(resp.json()['error'], f"snapshot with name {non_existing_snapshot} not found") diff --git a/system/t12_api/storage.py b/system/t12_api/storage.py new file mode 100644 index 00000000..4f6f3ba0 --- /dev/null +++ b/system/t12_api/storage.py @@ -0,0 +1,11 @@ +from api_lib import APITest + + +class TaskAPITestSwaggerDocs(APITest): + """ + GET /docs + """ + + def check(self): + resp = self.get("/api/storage") + self.check_equal(resp.status_code, 200) diff --git a/system/t12_api/tasks.py b/system/t12_api/tasks.py index 5f184c07..8d393248 100644 --- a/system/t12_api/tasks.py +++ b/system/t12_api/tasks.py @@ -1,4 +1,5 @@ from api_lib import APITest + from .publish import DefaultSigningOptions @@ -9,8 +10,9 @@ class TaskAPITestParallelTasks(APITest): def _create_mirror(self, dist): mirror_name = self.random_name() mirror_desc = {'Name': mirror_name, - 'ArchiveURL': 'https://packagecloud.io/varnishcache/varnish30/debian/', + 'ArchiveURL': 'http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/', 'Distribution': dist, + 'Architectures': ["amd64", "i386"], 'Components': ['main']} mirror_desc['IgnoreSignatures'] = True resp = self.post("/api/mirrors", json=mirror_desc) @@ -18,9 +20,9 @@ class TaskAPITestParallelTasks(APITest): resp = self.put("/api/mirrors/" + mirror_name, json=mirror_desc, params={'_async': True}) self.check_equal(resp.status_code, 202) - # check that two mirror updates cannot run at the same time + # check that two mirror updates are queuedd resp2 = self.put("/api/mirrors/" + mirror_name, json=mirror_desc, params={'_async': True}) - self.check_equal(resp2.status_code, 409) + self.check_equal(resp2.status_code, 202) return resp.json()['ID'], mirror_name @@ -47,9 +49,8 @@ class TaskAPITestParallelTasks(APITest): def _wait_for_task(self, task_id): uri = "/api/tasks/%d/wait" % int(task_id) - resp = self.get(uri) - self.check_equal(resp.status_code, 200) - self.check_equal(resp.json()['State'], 2) + task = self.get(uri) + self.check_task(task) def _wait_for_all_tasks(self): resp = self.get("/api/tasks-wait") @@ -75,7 +76,7 @@ class TaskAPITestParallelTasks(APITest): def check(self): publish_task_ids = [] mirror_task_list = [] - for mirror_dist in ['squeeze', 'jessie']: + for mirror_dist in ['squeeze', 'wheezy']: mirror_task_id, mirror_name = self._create_mirror(mirror_dist) mirror_task_list.append((mirror_task_id, mirror_name)) repo_task_id, repo_name = self._create_repo() @@ -101,6 +102,5 @@ class TaskAPITestParallelTasks(APITest): self._wait_for_all_tasks() for publish_task_id in publish_task_ids: - resp = self.get("/api/tasks/%d" % publish_task_id) - self.check_equal(resp.status_code, 200) - self.check_equal(resp.json()['State'], 2) + task = self.get("/api/tasks/%d" % publish_task_id) + self.check_task(task) diff --git a/system/t12_api/unix_socket.py b/system/t12_api/unix_socket.py index b14fe946..075ff688 100644 --- a/system/t12_api/unix_socket.py +++ b/system/t12_api/unix_socket.py @@ -6,19 +6,28 @@ import urllib.parse import urllib.request from lib import BaseTest +from testout import TestOut class UnixSocketAPITest(BaseTest): aptly_server = None socket_path = "/tmp/_aptly_test.sock" base_url = ("unix://%s" % socket_path) + aptly_out = None + debugOutput = True def prepare(self): if self.aptly_server is None: - self.aptly_server = self._start_process("aptly api serve -no-lock -listen=%s" % (self.base_url),) + UnixSocketAPITest.aptly_out = TestOut() + self.aptly_server = self._start_process("aptly api serve -no-lock -listen=%s" % (self.base_url), stdout=UnixSocketAPITest.aptly_out, stderr=UnixSocketAPITest.aptly_out) time.sleep(1) + else: + UnixSocketAPITest.aptly_out.clear() super(UnixSocketAPITest, self).prepare() + def debug_output(self): + return UnixSocketAPITest.aptly_out.get_contents() + def shutdown(self): if self.aptly_server is not None: self.aptly_server.terminate() diff --git a/system/t13_etcd/PublishDrop1TestEtcd_gold b/system/t13_etcd/PublishDrop1TestEtcd_gold new file mode 120000 index 00000000..e96691b2 --- /dev/null +++ b/system/t13_etcd/PublishDrop1TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop1Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop2TestEtcd_gold b/system/t13_etcd/PublishDrop2TestEtcd_gold new file mode 120000 index 00000000..e507a299 --- /dev/null +++ b/system/t13_etcd/PublishDrop2TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop2Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop3TestEtcd_gold b/system/t13_etcd/PublishDrop3TestEtcd_gold new file mode 120000 index 00000000..b371d5c0 --- /dev/null +++ b/system/t13_etcd/PublishDrop3TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop3Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop4TestEtcd_gold b/system/t13_etcd/PublishDrop4TestEtcd_gold new file mode 120000 index 00000000..34a46620 --- /dev/null +++ b/system/t13_etcd/PublishDrop4TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop4Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop5TestEtcd_gold b/system/t13_etcd/PublishDrop5TestEtcd_gold new file mode 120000 index 00000000..3ef73804 --- /dev/null +++ b/system/t13_etcd/PublishDrop5TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop5Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop6TestEtcd_gold b/system/t13_etcd/PublishDrop6TestEtcd_gold new file mode 120000 index 00000000..471ba43d --- /dev/null +++ b/system/t13_etcd/PublishDrop6TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop6Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop7TestEtcd_gold b/system/t13_etcd/PublishDrop7TestEtcd_gold new file mode 120000 index 00000000..a22742fc --- /dev/null +++ b/system/t13_etcd/PublishDrop7TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop7Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop8TestEtcd_gold b/system/t13_etcd/PublishDrop8TestEtcd_gold new file mode 120000 index 00000000..e20efea8 --- /dev/null +++ b/system/t13_etcd/PublishDrop8TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop8Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishDrop9TestEtcd_gold b/system/t13_etcd/PublishDrop9TestEtcd_gold new file mode 120000 index 00000000..89842314 --- /dev/null +++ b/system/t13_etcd/PublishDrop9TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishDrop9Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishList1TestEtcd_gold b/system/t13_etcd/PublishList1TestEtcd_gold new file mode 120000 index 00000000..c9ff7952 --- /dev/null +++ b/system/t13_etcd/PublishList1TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishList1Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishList2TestEtcd_gold b/system/t13_etcd/PublishList2TestEtcd_gold new file mode 120000 index 00000000..383fe5b8 --- /dev/null +++ b/system/t13_etcd/PublishList2TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishList2Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishList3TestEtcd_gold b/system/t13_etcd/PublishList3TestEtcd_gold new file mode 120000 index 00000000..09ac06cf --- /dev/null +++ b/system/t13_etcd/PublishList3TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishList3Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishList4TestEtcd_gold b/system/t13_etcd/PublishList4TestEtcd_gold new file mode 120000 index 00000000..f5b26f53 --- /dev/null +++ b/system/t13_etcd/PublishList4TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishList4Test_gold \ No newline at end of file diff --git a/system/t13_etcd/PublishList5TestEtcd_gold b/system/t13_etcd/PublishList5TestEtcd_gold new file mode 120000 index 00000000..eb94ffde --- /dev/null +++ b/system/t13_etcd/PublishList5TestEtcd_gold @@ -0,0 +1 @@ +../t06_publish/PublishList5Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror10TestEtcd_gold b/system/t13_etcd/UpdateMirror10TestEtcd_gold new file mode 120000 index 00000000..efa730fa --- /dev/null +++ b/system/t13_etcd/UpdateMirror10TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror10Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror11TestEtcd_gold b/system/t13_etcd/UpdateMirror11TestEtcd_gold new file mode 120000 index 00000000..9fb5a6a7 --- /dev/null +++ b/system/t13_etcd/UpdateMirror11TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror11Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror12TestEtcd_gold b/system/t13_etcd/UpdateMirror12TestEtcd_gold new file mode 120000 index 00000000..653d9a72 --- /dev/null +++ b/system/t13_etcd/UpdateMirror12TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror12Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror13TestEtcd_gold b/system/t13_etcd/UpdateMirror13TestEtcd_gold new file mode 120000 index 00000000..e5f8e8cf --- /dev/null +++ b/system/t13_etcd/UpdateMirror13TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror13Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror14TestEtcd_gold b/system/t13_etcd/UpdateMirror14TestEtcd_gold new file mode 120000 index 00000000..0f7bf746 --- /dev/null +++ b/system/t13_etcd/UpdateMirror14TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror14Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror15TestEtcd_gold b/system/t13_etcd/UpdateMirror15TestEtcd_gold new file mode 120000 index 00000000..bae34dff --- /dev/null +++ b/system/t13_etcd/UpdateMirror15TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror15Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror16TestEtcd_gold b/system/t13_etcd/UpdateMirror16TestEtcd_gold new file mode 120000 index 00000000..edbe7f22 --- /dev/null +++ b/system/t13_etcd/UpdateMirror16TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror16Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror17TestEtcd_gold b/system/t13_etcd/UpdateMirror17TestEtcd_gold new file mode 120000 index 00000000..eb1abbe6 --- /dev/null +++ b/system/t13_etcd/UpdateMirror17TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror17Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror18TestEtcd_gold b/system/t13_etcd/UpdateMirror18TestEtcd_gold new file mode 120000 index 00000000..c0333d25 --- /dev/null +++ b/system/t13_etcd/UpdateMirror18TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror18Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror19TestEtcd_gold b/system/t13_etcd/UpdateMirror19TestEtcd_gold new file mode 120000 index 00000000..a178fe7b --- /dev/null +++ b/system/t13_etcd/UpdateMirror19TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror19Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror1TestEtcd_gold b/system/t13_etcd/UpdateMirror1TestEtcd_gold new file mode 120000 index 00000000..cc5035be --- /dev/null +++ b/system/t13_etcd/UpdateMirror1TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror1Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror20TestEtcd_gold b/system/t13_etcd/UpdateMirror20TestEtcd_gold new file mode 120000 index 00000000..444b11de --- /dev/null +++ b/system/t13_etcd/UpdateMirror20TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror20Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror21TestEtcd_gold b/system/t13_etcd/UpdateMirror21TestEtcd_gold new file mode 120000 index 00000000..bff753d0 --- /dev/null +++ b/system/t13_etcd/UpdateMirror21TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror21Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror22TestEtcd_gold b/system/t13_etcd/UpdateMirror22TestEtcd_gold new file mode 120000 index 00000000..54697e8c --- /dev/null +++ b/system/t13_etcd/UpdateMirror22TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror22Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror23TestEtcd_gold b/system/t13_etcd/UpdateMirror23TestEtcd_gold new file mode 120000 index 00000000..649ac10f --- /dev/null +++ b/system/t13_etcd/UpdateMirror23TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror23Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror24TestEtcd_gold b/system/t13_etcd/UpdateMirror24TestEtcd_gold new file mode 120000 index 00000000..31721619 --- /dev/null +++ b/system/t13_etcd/UpdateMirror24TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror24Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror25TestEtcd_gold b/system/t13_etcd/UpdateMirror25TestEtcd_gold new file mode 120000 index 00000000..a3a057cf --- /dev/null +++ b/system/t13_etcd/UpdateMirror25TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror25Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror2TestEtcd_gold b/system/t13_etcd/UpdateMirror2TestEtcd_gold new file mode 120000 index 00000000..9bf2cf84 --- /dev/null +++ b/system/t13_etcd/UpdateMirror2TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror2Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror3TestEtcd_gold b/system/t13_etcd/UpdateMirror3TestEtcd_gold new file mode 120000 index 00000000..3a18595b --- /dev/null +++ b/system/t13_etcd/UpdateMirror3TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror3Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror4TestEtcd_gold b/system/t13_etcd/UpdateMirror4TestEtcd_gold new file mode 120000 index 00000000..33efda8a --- /dev/null +++ b/system/t13_etcd/UpdateMirror4TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror4Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror5TestEtcd_gold b/system/t13_etcd/UpdateMirror5TestEtcd_gold new file mode 120000 index 00000000..3dd08aed --- /dev/null +++ b/system/t13_etcd/UpdateMirror5TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror5Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror6TestEtcd_gold b/system/t13_etcd/UpdateMirror6TestEtcd_gold new file mode 120000 index 00000000..3ddffa3a --- /dev/null +++ b/system/t13_etcd/UpdateMirror6TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror6Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror7TestEtcd_gold b/system/t13_etcd/UpdateMirror7TestEtcd_gold new file mode 120000 index 00000000..283a8c0c --- /dev/null +++ b/system/t13_etcd/UpdateMirror7TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror7Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror8TestEtcd_gold b/system/t13_etcd/UpdateMirror8TestEtcd_gold new file mode 120000 index 00000000..2cb66953 --- /dev/null +++ b/system/t13_etcd/UpdateMirror8TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror8Test_gold \ No newline at end of file diff --git a/system/t13_etcd/UpdateMirror9TestEtcd_gold b/system/t13_etcd/UpdateMirror9TestEtcd_gold new file mode 120000 index 00000000..4105a0d6 --- /dev/null +++ b/system/t13_etcd/UpdateMirror9TestEtcd_gold @@ -0,0 +1 @@ +../t04_mirror/UpdateMirror9Test_gold \ No newline at end of file diff --git a/system/t13_etcd/__init__.py b/system/t13_etcd/__init__.py new file mode 100644 index 00000000..d3a06023 --- /dev/null +++ b/system/t13_etcd/__init__.py @@ -0,0 +1,3 @@ +""" +Testing aptly with etcd DB +""" diff --git a/system/t13_etcd/install-etcd.sh b/system/t13_etcd/install-etcd.sh new file mode 100755 index 00000000..1521511d --- /dev/null +++ b/system/t13_etcd/install-etcd.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# etcd test env +ETCD_VER=v3.5.2 +DOWNLOAD_URL=https://storage.googleapis.com/etcd + +ARCH="" +case $(uname -m) in + x86_64) ARCH="amd64" ;; + aarch64) ARCH="arm64" ;; + *) echo "unsupported cpu arch"; exit 1 ;; +esac + +if [ ! -e /tmp/etcd-${ETCD_VER}-linux-$ARCH.tar.gz ]; then + curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-$ARCH.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-$ARCH.tar.gz +fi + +mkdir /tmp/aptly-etcd +tar xf /tmp/etcd-${ETCD_VER}-linux-$ARCH.tar.gz -C /tmp/aptly-etcd --strip-components=1 diff --git a/system/t13_etcd/mirror_update.py b/system/t13_etcd/mirror_update.py new file mode 100644 index 00000000..2e4a04b1 --- /dev/null +++ b/system/t13_etcd/mirror_update.py @@ -0,0 +1,224 @@ +# reuse existing tests: +from t04_mirror.update import UpdateMirror1Test, \ + UpdateMirror2Test, \ + UpdateMirror3Test, \ + UpdateMirror4Test, \ + UpdateMirror5Test, \ + UpdateMirror6Test, \ + UpdateMirror7Test, \ + UpdateMirror8Test, \ + UpdateMirror9Test, \ + UpdateMirror10Test, \ + UpdateMirror12Test, \ + UpdateMirror13Test, \ + UpdateMirror14Test, \ + UpdateMirror17Test, \ + UpdateMirror18Test, \ + UpdateMirror19Test, \ + UpdateMirror20Test, \ + UpdateMirror21Test, \ + UpdateMirror22Test, \ + UpdateMirror23Test, \ + UpdateMirror24Test, \ + UpdateMirror25Test + + +TEST_IGNORE = ["UpdateMirror1Test", + "UpdateMirror2Test", + "UpdateMirror3Test", + "UpdateMirror4Test", + "UpdateMirror5Test", + "UpdateMirror6Test", + "UpdateMirror7Test", + "UpdateMirror8Test", + "UpdateMirror9Test", + "UpdateMirror10Test", + "UpdateMirror12Test", + "UpdateMirror13Test", + "UpdateMirror14Test", + "UpdateMirror17Test", + "UpdateMirror18Test", + "UpdateMirror19Test", + "UpdateMirror20Test", + "UpdateMirror21Test", + "UpdateMirror22Test", + "UpdateMirror23Test", + "UpdateMirror24Test", + "UpdateMirror25Test" + ] + + +class UpdateMirror1TestEtcd(UpdateMirror1Test): + """ + update mirrors: regular update + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror2TestEtcd(UpdateMirror2Test): + """ + update mirrors: no such repo + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror3TestEtcd(UpdateMirror3Test): + """ + update mirrors: wrong checksum in release file + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror4TestEtcd(UpdateMirror4Test): + """ + update mirrors: wrong checksum in release file, but ignore + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror5TestEtcd(UpdateMirror5Test): + """ + update mirrors: wrong checksum in package + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror6TestEtcd(UpdateMirror6Test): + """ + update mirrors: wrong checksum in package, but ignore + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror7TestEtcd(UpdateMirror7Test): + """ + update mirrors: flat repository + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror8TestEtcd(UpdateMirror8Test): + """ + update mirrors: with sources (already in pool) + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror9TestEtcd(UpdateMirror9Test): + """ + update mirrors: flat repository + sources + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror10TestEtcd(UpdateMirror10Test): + """ + update mirrors: filtered + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror12TestEtcd(UpdateMirror12Test): + """ + update mirrors: update with udebs + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror13TestEtcd(UpdateMirror13Test): + """ + update mirrors: regular update with --skip-existing-packages option + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror14TestEtcd(UpdateMirror14Test): + """ + update mirrors: regular update with --skip-existing-packages option + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror17TestEtcd(UpdateMirror17Test): + """ + update mirrors: update for mirror but with file in pool on legacy MD5 location + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror18TestEtcd(UpdateMirror18Test): + """ + update mirrors: update for mirror but with file in pool on legacy MD5 location and disabled legacy path support + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror19TestEtcd(UpdateMirror19Test): + """ + update mirrors: correct matching of Release checksums + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror20TestEtcd(UpdateMirror20Test): + """ + update mirrors: flat repository (internal GPG implementation) + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror21TestEtcd(UpdateMirror21Test): + """ + update mirrors: correct matching of Release checksums (internal pgp implementation) + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror22TestEtcd(UpdateMirror22Test): + """ + update mirrors: SHA512 checksums only + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror23TestEtcd(UpdateMirror23Test): + """ + update mirrors: update with installer + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror24TestEtcd(UpdateMirror24Test): + """ + update mirrors: update with installer with separate gpg file + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class UpdateMirror25TestEtcd(UpdateMirror25Test): + """ + update mirrors: mirror with / in distribution + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" diff --git a/system/t13_etcd/publish_drop.py b/system/t13_etcd/publish_drop.py new file mode 100644 index 00000000..8f9f60fc --- /dev/null +++ b/system/t13_etcd/publish_drop.py @@ -0,0 +1,92 @@ +# reuse existing tests: +from t06_publish.drop import PublishDrop1Test, \ + PublishDrop2Test, \ + PublishDrop3Test, \ + PublishDrop4Test, \ + PublishDrop5Test, \ + PublishDrop6Test, \ + PublishDrop7Test, \ + PublishDrop8Test, \ + PublishDrop9Test + +TEST_IGNORE = ["PublishDrop1Test", + "PublishDrop2Test", + "PublishDrop3Test", + "PublishDrop4Test", + "PublishDrop5Test", + "PublishDrop6Test", + "PublishDrop7Test", + "PublishDrop8Test", + "PublishDrop9Test"] + + +class PublishDrop1TestEtcd(PublishDrop1Test): + """ + publish drop: existing snapshot + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop2TestEtcd(PublishDrop2Test): + """ + publish drop: under prefix + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop3TestEtcd(PublishDrop3Test): + """ + publish drop: drop one distribution + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop4TestEtcd(PublishDrop4Test): + """ + publish drop: drop one of components + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop5TestEtcd(PublishDrop5Test): + """ + publish drop: component cleanup + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop6TestEtcd(PublishDrop6Test): + """ + publish drop: no publish + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop7TestEtcd(PublishDrop7Test): + """ + publish drop: under prefix with trailing & leading slashes + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop8TestEtcd(PublishDrop8Test): + """ + publish drop: skip component cleanup + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishDrop9TestEtcd(PublishDrop9Test): + """ + publish drop: component cleanup after first cleanup skipped + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" diff --git a/system/t13_etcd/publish_list.py b/system/t13_etcd/publish_list.py new file mode 100644 index 00000000..8825d05e --- /dev/null +++ b/system/t13_etcd/publish_list.py @@ -0,0 +1,48 @@ +# reuse existing tests: +from t06_publish.list import PublishList1Test, \ + PublishList2Test, \ + PublishList3Test, \ + PublishList4Test, \ + PublishList5Test + +TEST_IGNORE = ["PublishList1Test", "PublishList2Test", "PublishList3Test", "PublishList4Test", "PublishList5Test"] + + +class PublishList1TestEtcd(PublishList1Test): + """ + publish list: empty list + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishList2TestEtcd(PublishList2Test): + """ + publish list: several repos list + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishList3TestEtcd(PublishList3Test): + """ + publish list: several repos list, raw + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishList4TestEtcd(PublishList4Test): + """ + publish list json: empty list + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" + + +class PublishList5TestEtcd(PublishList5Test): + """ + publish list json: several repos list + """ + databaseType = "etcd" + databaseUrl = "127.0.0.1:2379" diff --git a/system/t13_etcd/start-etcd.sh b/system/t13_etcd/start-etcd.sh new file mode 100755 index 00000000..05908def --- /dev/null +++ b/system/t13_etcd/start-etcd.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +if [ -e /tmp/etcd.pid ]; then + echo etcd already running, killing.. + etcdpid=`cat /tmp/etcd.pid` + kill $etcdpid + sleep 2 +fi + +finish() +{ + if [ -n "$etcdpid" ]; then + echo terminating etcd + kill $etcdpid + fi +} +trap finish INT + +/tmp/aptly-etcd/etcd --max-request-bytes '1073741824' --data-dir /tmp/aptly-etcd-data & +echo $! > /tmp/etcd.pid +etcdpid=`cat /tmp/etcd.pid` +wait $etcdpid +echo etcd terminated diff --git a/system/t13_etcd/test_release b/system/t13_etcd/test_release new file mode 120000 index 00000000..e8d20bcb --- /dev/null +++ b/system/t13_etcd/test_release @@ -0,0 +1 @@ +../t04_mirror/test_release \ No newline at end of file diff --git a/system/t13_etcd/test_release2 b/system/t13_etcd/test_release2 new file mode 120000 index 00000000..a77f3071 --- /dev/null +++ b/system/t13_etcd/test_release2 @@ -0,0 +1 @@ +../t04_mirror/test_release2 \ No newline at end of file diff --git a/system/t14_graph/CreateGraphOutputTest_gold b/system/t14_graph/CreateGraphOutputTest_gold new file mode 100644 index 00000000..e9ca9ebe --- /dev/null +++ b/system/t14_graph/CreateGraphOutputTest_gold @@ -0,0 +1,2 @@ +Generating graph... +Output saved to /tmp/aptly-graph.png diff --git a/system/t14_graph/CreateGraphTest_gold b/system/t14_graph/CreateGraphTest_gold new file mode 100644 index 00000000..12928d29 --- /dev/null +++ b/system/t14_graph/CreateGraphTest_gold @@ -0,0 +1,2 @@ +Generating graph... +Displaying png file: xdg-open /tmp/aptly-graph1173098610.png diff --git a/system/t14_graph/graph.py b/system/t14_graph/graph.py new file mode 100644 index 00000000..ba864275 --- /dev/null +++ b/system/t14_graph/graph.py @@ -0,0 +1,33 @@ +""" +Test aptly graph +""" + +import os +import re + +from lib import BaseTest + + +class CreateGraphTest(BaseTest): + """ + open graph in viewer + """ + fixtureCmds = ["mkdir -p ../build", "ln -fs /bin/true ../build/xdg-open"] + environmentOverride = {"PATH": os.environ["PATH"] + ":../build"} + runCmd = "aptly graph" + + def outputMatchPrepare(self, s): + return re.sub(r"[0-9]", "", s) + + def teardown(self): + self.run_cmd(["rm", "-f", "../build/xdg-open"]) + + +class CreateGraphOutputTest(BaseTest): + """ + open graph in viewer + """ + runCmd = "aptly graph -output /tmp/aptly-graph.png" + + def teardown(self): + self.run_cmd(["rm", "-f", "/tmp/aptly-graph.png"]) diff --git a/system/testout.py b/system/testout.py new file mode 100644 index 00000000..f7dc134d --- /dev/null +++ b/system/testout.py @@ -0,0 +1,23 @@ +import tempfile + + +class TestOut: + def __init__(self): + self.tmp_file = tempfile.NamedTemporaryFile(delete=False) + self.read_pos = 0 + + def fileno(self): + return self.tmp_file.fileno() + + def write(self, text): + self.tmp_file.write(text.encode()) + + def get_contents(self): + self.tmp_file.seek(self.read_pos, 0) + return self.tmp_file.read().decode("utf-8") + + def close(self): + self.tmp_file.close() + + def clear(self): + self.read_pos = self.tmp_file.tell() diff --git a/task/list.go b/task/list.go index 472282b7..3636850c 100644 --- a/task/list.go +++ b/task/list.go @@ -17,6 +17,10 @@ type List struct { // resources currently used by running tasks usedResources *ResourcesSet idCounter int + + queue chan *Task + queueWg *sync.WaitGroup + queueDone chan bool } // NewList creates empty task list @@ -27,13 +31,75 @@ func NewList() *List { wgTasks: make(map[int]*sync.WaitGroup), wg: &sync.WaitGroup{}, usedResources: NewResourcesSet(), + queue: make(chan *Task, 0), + queueWg: &sync.WaitGroup{}, + queueDone: make(chan bool), } + go list.consumer() return list } +// consumer is processing the queue +func (list *List) consumer() { + for { + select { + case task := <-list.queue: + list.Lock() + { + task.State = RUNNING + } + list.Unlock() + + go func() { + retValue, err := task.process(aptly.Progress(task.output), task.detail) + + list.Lock() + { + task.processReturnValue = retValue + task.err = err + if err != nil { + task.output.Printf("Task failed with error: %v", err) + task.State = FAILED + } else { + task.output.Print("Task succeeded") + task.State = SUCCEEDED + } + + list.usedResources.Free(task.resources) + + task.wgTask.Done() + list.wg.Done() + + for _, t := range list.tasks { + if t.State == IDLE { + // check resources + blockingTasks := list.usedResources.UsedBy(t.resources) + if len(blockingTasks) == 0 { + list.usedResources.MarkInUse(task.resources, task) + list.queue <- t + break + } + } + } + } + list.Unlock() + }() + + case <-list.queueDone: + return + } + } +} + +// Stop signals the consumer to stop processing tasks and waits for it to finish +func (list *List) Stop() { + close(list.queueDone) + list.queueWg.Wait() +} + // GetTasks gets complete list of tasks func (list *List) GetTasks() []Task { - var tasks []Task + tasks := []Task{} list.Lock() for _, task := range list.tasks { tasks = append(tasks, *task) @@ -117,60 +183,29 @@ func (list *List) GetTaskReturnValueByID(ID int) (*ProcessReturnValue, error) { return task.processReturnValue, nil } -// RunTaskInBackground creates task and runs it in background. It won't be run and an error -// returned if there are running tasks which are using needed resources already. +// RunTaskInBackground creates task and runs it in background. This will block until the necessary resources +// become available. func (list *List) RunTaskInBackground(name string, resources []string, process Process) (Task, *ResourceConflictError) { list.Lock() defer list.Unlock() - tasks := list.usedResources.UsedBy(resources) - if len(tasks) > 0 { - conflictError := &ResourceConflictError{ - Tasks: tasks, - Message: "Needed resources are used by other tasks.", - } - return Task{}, conflictError - } - list.idCounter++ wgTask := &sync.WaitGroup{} - task := NewTask(process, name, list.idCounter) + task := NewTask(process, name, list.idCounter, resources, wgTask) list.tasks = append(list.tasks, task) list.wgTasks[task.ID] = wgTask - list.usedResources.MarkInUse(resources, task) list.wg.Add(1) - wgTask.Add(1) + task.wgTask.Add(1) - go func() { - - list.Lock() - { - task.State = RUNNING - } - list.Unlock() - - retValue, err := process(aptly.Progress(task.output), task.detail) - - list.Lock() - { - task.processReturnValue = retValue - if err != nil { - task.output.Printf("Task failed with error: %v", err) - task.State = FAILED - } else { - task.output.Print("Task succeeded") - task.State = SUCCEEDED - } - - list.usedResources.Free(resources) - - wgTask.Done() - list.wg.Done() - } - list.Unlock() - }() + // add task to queue for processing if resources are available + // if not, task will be queued by the consumer once resources are available + tasks := list.usedResources.UsedBy(resources) + if len(tasks) == 0 { + list.usedResources.MarkInUse(task.resources, task) + list.queue <- task + } return *task, nil } @@ -207,3 +242,14 @@ func (list *List) WaitForTaskByID(ID int) (Task, error) { wgTask.Wait() return list.GetTaskByID(ID) } + +// GetTaskError returns the Task error for a given id +func (list *List) GetTaskErrorByID(ID int) (error, error) { + task, err := list.GetTaskByID(ID) + + if err != nil { + return nil, err + } + + return task.err, nil +} diff --git a/task/list_test.go b/task/list_test.go index 297e97e2..bf5d3efc 100644 --- a/task/list_test.go +++ b/task/list_test.go @@ -50,4 +50,5 @@ func (s *ListSuite) TestList(c *check.C) { c.Check(detail, check.Equals, "Details") _, deleteErr := list.DeleteTaskByID(task.ID) c.Check(deleteErr, check.IsNil) + list.Stop() } diff --git a/task/output.go b/task/output.go index cd20ff73..f4c5e8f7 100644 --- a/task/output.go +++ b/task/output.go @@ -63,12 +63,12 @@ func (t *Output) Flush() { } // InitBar is needed for progress compatibility -func (t *Output) InitBar(count int64, isBytes bool, barType aptly.BarType) { +func (t *Output) InitBar(_ int64, _ bool, _ aptly.BarType) { // Not implemented } // InitBar publish output specific -func (t *PublishOutput) InitBar(count int64, isBytes bool, barType aptly.BarType) { +func (t *PublishOutput) InitBar(count int64, _ bool, barType aptly.BarType) { t.barType = &barType if barType == aptly.BarPublishGeneratePackageFiles { t.TotalNumberOfPackages = count @@ -88,12 +88,12 @@ func (t *PublishOutput) ShutdownBar() { } // AddBar is needed for progress compatibility -func (t *Output) AddBar(count int) { +func (t *Output) AddBar(_ int) { // Not implemented } // AddBar publish output specific -func (t *PublishOutput) AddBar(count int) { +func (t *PublishOutput) AddBar(_ int) { if t.barType != nil && *t.barType == aptly.BarPublishGeneratePackageFiles { t.RemainingNumberOfPackages-- t.Store(t) @@ -101,7 +101,7 @@ func (t *PublishOutput) AddBar(count int) { } // SetBar sets current position for progress bar -func (t *Output) SetBar(count int) { +func (t *Output) SetBar(_ int) { // Not implemented } @@ -123,5 +123,5 @@ func (t *Output) ColoredPrintf(msg string, a ...interface{}) { // PrintfStdErr does printf but in safe manner to output func (t *Output) PrintfStdErr(msg string, a ...interface{}) { - t.WriteString(msg) + t.WriteString(fmt.Sprintf(msg, a...)) } diff --git a/task/task.go b/task/task.go index 54da0e3c..02aa7037 100644 --- a/task/task.go +++ b/task/task.go @@ -1,6 +1,7 @@ package task import ( + "sync" "sync/atomic" "github.com/aptly-dev/aptly/aptly" @@ -46,20 +47,25 @@ type Task struct { detail *Detail process Process processReturnValue *ProcessReturnValue + err error Name string ID int State State + resources []string + wgTask *sync.WaitGroup } // NewTask creates new task -func NewTask(process Process, name string, ID int) *Task { +func NewTask(process Process, name string, ID int, resources []string, wgTask *sync.WaitGroup) *Task { task := &Task{ - output: NewOutput(), - detail: &Detail{}, - process: process, - Name: name, - ID: ID, - State: IDLE, + output: NewOutput(), + detail: &Detail{}, + process: process, + Name: name, + ID: ID, + State: IDLE, + resources: resources, + wgTask: wgTask, } return task } diff --git a/upload-artifacts.sh b/upload-artifacts.sh deleted file mode 100755 index 3a47db3f..00000000 --- a/upload-artifacts.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh - -set -e - -builds=build/ -packages=${builds}*.deb -folder=`mktemp -u tmp.XXXXXXXXXXXXXXX` -aptly_user="$APTLY_USER" -aptly_password="$APTLY_PASSWORD" -aptly_api="https://aptly-ops.aptly.info" -version=`make version` - -echo "Publishing version '$version' to $1..." - -for file in $packages; do - echo "Uploading $file..." - curl -fsS -X POST -F "file=@$file" -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder - echo -done - -if [ "$1" = "nightly" ]; then - if echo "$version" | grep -vq "+"; then - # skip nightly when on release tag - exit 0 - fi - - aptly_repository=aptly-nightly - aptly_published=s3:repo.aptly.info:./nightly - - echo "Adding packages to $aptly_repository..." - curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder - echo - - echo "Updating published repo..." - curl -fsS -X PUT -H 'Content-Type: application/json' --data \ - '{"AcquireByHash": true, "Signing": {"Batch": true, "Keyring": "aptly.repo/aptly.pub", - "secretKeyring": "aptly.repo/aptly.sec", "PassphraseFile": "aptly.repo/passphrase"}}' \ - -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$aptly_published - echo -fi - -if [ "$1" = "release" ]; then - aptly_repository=aptly-release - aptly_snapshot=aptly-$version - aptly_published=s3:repo.aptly.info:./squeeze - - echo "Adding packages to $aptly_repository..." - curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder - echo - - echo "Creating snapshot $aptly_snapshot from repo $aptly_repository..." - curl -fsS -X POST -u $aptly_user:$aptly_password -H 'Content-Type: application/json' --data \ - "{\"Name\":\"$aptly_snapshot\"}" ${aptly_api}/api/repos/$aptly_repository/snapshots - echo - - echo "Switching published repo to use snapshot $aptly_snapshot..." - curl -fsS -X PUT -H 'Content-Type: application/json' --data \ - "{\"AcquireByHash\": true, \"Snapshots\": [{\"Component\": \"main\", \"Name\": \"$aptly_snapshot\"}], - \"Signing\": {\"Batch\": true, \"Keyring\": \"aptly.repo/aptly.pub\", - \"secretKeyring\": \"aptly.repo/aptly.sec\", \"PassphraseFile\": \"aptly.repo/passphrase\"}}" \ - -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$aptly_published - echo -fi - -curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder -echo diff --git a/utils/checksum.go b/utils/checksum.go index 0009ea23..6e1a9515 100644 --- a/utils/checksum.go +++ b/utils/checksum.go @@ -42,7 +42,21 @@ func (cksum *ChecksumInfo) Complete() bool { return cksum.MD5 != "" && cksum.SHA1 != "" && cksum.SHA256 != "" && cksum.SHA512 != "" } -// ChecksumsForFile generates size, MD5, SHA1 & SHA256 checksums for given file +// ChecksumsForReader generates size, MD5, SHA1 & SHA256 checksums for the given +// io.Reader +func ChecksumsForReader(rd io.Reader) (ChecksumInfo, error) { + w := NewChecksumWriter() + + _, err := io.Copy(w, rd) + if err != nil { + return ChecksumInfo{}, err + } + + return w.Sum(), nil +} + +// ChecksumsForFile generates size, MD5, SHA1 & SHA256 checksums for the file at +// the given path func ChecksumsForFile(path string) (ChecksumInfo, error) { file, err := os.Open(path) if err != nil { @@ -50,14 +64,7 @@ func ChecksumsForFile(path string) (ChecksumInfo, error) { } defer file.Close() - w := NewChecksumWriter() - - _, err = io.Copy(w, file) - if err != nil { - return ChecksumInfo{}, err - } - - return w.Sum(), nil + return ChecksumsForReader(file) } // ChecksumWriter is a writer that does checksum calculation on the fly passing data diff --git a/utils/checksum_test.go b/utils/checksum_test.go index a29d602f..9e41e487 100644 --- a/utils/checksum_test.go +++ b/utils/checksum_test.go @@ -1,7 +1,6 @@ package utils import ( - "io/ioutil" "os" . "gopkg.in/check.v1" @@ -14,7 +13,7 @@ type ChecksumSuite struct { var _ = Suite(&ChecksumSuite{}) func (s *ChecksumSuite) SetUpTest(c *C) { - s.tempfile, _ = ioutil.TempFile(c.MkDir(), "aptly-test") + s.tempfile, _ = os.CreateTemp(c.MkDir(), "aptly-test") s.tempfile.WriteString(testString) } diff --git a/utils/compress_test.go b/utils/compress_test.go index 1a02b354..e5b42274 100644 --- a/utils/compress_test.go +++ b/utils/compress_test.go @@ -18,7 +18,7 @@ var _ = Suite(&CompressSuite{}) const testString = "Quick brown fox jumps over black dog and runs away... Really far away... who knows?" func (s *CompressSuite) SetUpTest(c *C) { - s.tempfile, _ = ioutil.TempFile(c.MkDir(), "aptly-test") + s.tempfile, _ = os.CreateTemp(c.MkDir(), "aptly-test") s.tempfile.WriteString(testString) } diff --git a/utils/config.go b/utils/config.go index 2b60d6c6..da7dfcd6 100644 --- a/utils/config.go +++ b/utils/config.go @@ -2,88 +2,213 @@ package utils import ( "encoding/json" + "fmt" "os" "path/filepath" + "strings" + + "github.com/DisposaBoy/JsonConfigReader" + "gopkg.in/yaml.v3" ) // ConfigStructure is structure of main configuration type ConfigStructure struct { // nolint: maligned - RootDir string `json:"rootDir"` - DownloadConcurrency int `json:"downloadConcurrency"` - DownloadLimit int64 `json:"downloadSpeedLimit"` - DownloadRetries int `json:"downloadRetries"` - Downloader string `json:"downloader"` - DatabaseOpenAttempts int `json:"databaseOpenAttempts"` - Architectures []string `json:"architectures"` - DepFollowSuggests bool `json:"dependencyFollowSuggests"` - DepFollowRecommends bool `json:"dependencyFollowRecommends"` - DepFollowAllVariants bool `json:"dependencyFollowAllVariants"` - DepFollowSource bool `json:"dependencyFollowSource"` - DepVerboseResolve bool `json:"dependencyVerboseResolve"` - GpgDisableSign bool `json:"gpgDisableSign"` - GpgDisableVerify bool `json:"gpgDisableVerify"` - GpgProvider string `json:"gpgProvider"` - DownloadSourcePackages bool `json:"downloadSourcePackages"` - SkipLegacyPool bool `json:"skipLegacyPool"` - PpaDistributorID string `json:"ppaDistributorID"` - PpaCodename string `json:"ppaCodename"` - SkipContentsPublishing bool `json:"skipContentsPublishing"` - SkipBz2Publishing bool `json:"skipBz2Publishing"` - FileSystemPublishRoots map[string]FileSystemPublishRoot `json:"FileSystemPublishEndpoints"` - S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints"` - SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints"` - AzurePublishRoots map[string]AzurePublishRoot `json:"AzurePublishEndpoints"` - AsyncAPI bool `json:"AsyncAPI"` - EnableMetricsEndpoint bool `json:"enableMetricsEndpoint"` + // General + RootDir string `json:"rootDir" yaml:"root_dir"` + LogLevel string `json:"logLevel" yaml:"log_level"` + LogFormat string `json:"logFormat" yaml:"log_format"` + DatabaseOpenAttempts int `json:"databaseOpenAttempts" yaml:"database_open_attempts"` + Architectures []string `json:"architectures" yaml:"architectures"` + SkipLegacyPool bool `json:"skipLegacyPool" yaml:"skip_legacy_pool"` // OBSOLETE + + // Dependency following + DepFollowSuggests bool `json:"dependencyFollowSuggests" yaml:"dep_follow_suggests"` + DepFollowRecommends bool `json:"dependencyFollowRecommends" yaml:"dep_follow_recommends"` + DepFollowAllVariants bool `json:"dependencyFollowAllVariants" yaml:"dep_follow_all_variants"` + DepFollowSource bool `json:"dependencyFollowSource" yaml:"dep_follow_source"` + DepVerboseResolve bool `json:"dependencyVerboseResolve" yaml:"dep_verboseresolve"` + + // PPA + PpaDistributorID string `json:"ppaDistributorID" yaml:"ppa_distributor_id"` + PpaCodename string `json:"ppaCodename" yaml:"ppa_codename"` + + // Server + ServeInAPIMode bool `json:"serveInAPIMode" yaml:"serve_in_api_mode"` + EnableMetricsEndpoint bool `json:"enableMetricsEndpoint" yaml:"enable_metrics_endpoint"` + EnableSwaggerEndpoint bool `json:"enableSwaggerEndpoint" yaml:"enable_swagger_endpoint"` + AsyncAPI bool `json:"AsyncAPI" yaml:"async_api"` // OBSOLETE + + // Database + DatabaseBackend DBConfig `json:"databaseBackend" yaml:"database_backend"` + + // Mirroring + Downloader string `json:"downloader" yaml:"downloader"` + DownloadConcurrency int `json:"downloadConcurrency" yaml:"download_concurrency"` + DownloadLimit int64 `json:"downloadSpeedLimit" yaml:"download_limit"` + DownloadRetries int `json:"downloadRetries" yaml:"download_retries"` + DownloadSourcePackages bool `json:"downloadSourcePackages" yaml:"download_sourcepackages"` + + // Signing + GpgProvider string `json:"gpgProvider" yaml:"gpg_provider"` + GpgDisableSign bool `json:"gpgDisableSign" yaml:"gpg_disable_sign"` + GpgDisableVerify bool `json:"gpgDisableVerify" yaml:"gpg_disable_verify"` + + // Publishing + SkipContentsPublishing bool `json:"skipContentsPublishing" yaml:"skip_contents_publishing"` + SkipBz2Publishing bool `json:"skipBz2Publishing" yaml:"skip_bz2_publishing"` + + // Storage + FileSystemPublishRoots map[string]FileSystemPublishRoot `json:"FileSystemPublishEndpoints" yaml:"filesystem_publish_endpoints"` + S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints" yaml:"s3_publish_endpoints"` + SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints" yaml:"swift_publish_endpoints"` + AzurePublishRoots map[string]AzureEndpoint `json:"AzurePublishEndpoints" yaml:"azure_publish_endpoints"` + PackagePoolStorage PackagePoolStorage `json:"packagePoolStorage" yaml:"packagepool_storage"` +} + +// DBConfig +type DBConfig struct { + Type string `json:"type" yaml:"type"` + DbPath string `json:"dbPath" yaml:"db_path"` + URL string `json:"url" yaml:"url"` +} + +type LocalPoolStorage struct { + Path string `json:"path,omitempty" yaml:"path,omitempty"` +} + +type PackagePoolStorage struct { + Local *LocalPoolStorage + Azure *AzureEndpoint +} + +var AZURE = "azure" +var LOCAL = "local" + +func (pool *PackagePoolStorage) UnmarshalJSON(data []byte) error { + var discriminator struct { + Type string `json:"type"` + } + + if err := json.Unmarshal(data, &discriminator); err != nil { + return err + } + + switch discriminator.Type { + case AZURE: + pool.Azure = &AzureEndpoint{} + return json.Unmarshal(data, &pool.Azure) + case LOCAL, "": + pool.Local = &LocalPoolStorage{} + return json.Unmarshal(data, &pool.Local) + default: + return fmt.Errorf("unknown pool storage type: %s", discriminator.Type) + } +} + +func (pool *PackagePoolStorage) UnmarshalYAML(unmarshal func(interface{}) error) error { + var discriminator struct { + Type string `yaml:"type"` + } + if err := unmarshal(&discriminator); err != nil { + return err + } + + switch discriminator.Type { + case AZURE: + pool.Azure = &AzureEndpoint{} + return unmarshal(&pool.Azure) + case LOCAL, "": + pool.Local = &LocalPoolStorage{} + return unmarshal(&pool.Local) + default: + return fmt.Errorf("unknown pool storage type: %s", discriminator.Type) + } +} + +func (pool *PackagePoolStorage) MarshalJSON() ([]byte, error) { + var wrapper struct { + Type string `json:"type,omitempty"` + *LocalPoolStorage + *AzureEndpoint + } + + if pool.Azure != nil { + wrapper.Type = "azure" + wrapper.AzureEndpoint = pool.Azure + } else if pool.Local.Path != "" { + wrapper.Type = "local" + wrapper.LocalPoolStorage = pool.Local + } + + return json.Marshal(wrapper) +} + +func (pool PackagePoolStorage) MarshalYAML() (interface{}, error) { + var wrapper struct { + Type string `yaml:"type,omitempty"` + *LocalPoolStorage `yaml:",inline"` + *AzureEndpoint `yaml:",inline"` + } + + if pool.Azure != nil { + wrapper.Type = "azure" + wrapper.AzureEndpoint = pool.Azure + } else if pool.Local.Path != "" { + wrapper.Type = "local" + wrapper.LocalPoolStorage = pool.Local + } + + return wrapper, nil } // FileSystemPublishRoot describes single filesystem publishing entry point type FileSystemPublishRoot struct { - RootDir string `json:"rootDir"` - LinkMethod string `json:"linkMethod"` - VerifyMethod string `json:"verifyMethod"` + RootDir string `json:"rootDir" yaml:"root_dir"` + LinkMethod string `json:"linkMethod" yaml:"link_method"` + VerifyMethod string `json:"verifyMethod" yaml:"verify_method"` } // S3PublishRoot describes single S3 publishing entry point type S3PublishRoot struct { - Region string `json:"region"` - Bucket string `json:"bucket"` - Endpoint string `json:"endpoint"` - AccessKeyID string `json:"awsAccessKeyID"` - SecretAccessKey string `json:"awsSecretAccessKey"` - SessionToken string `json:"awsSessionToken"` - Prefix string `json:"prefix"` - ACL string `json:"acl"` - StorageClass string `json:"storageClass"` - EncryptionMethod string `json:"encryptionMethod"` - PlusWorkaround bool `json:"plusWorkaround"` - DisableMultiDel bool `json:"disableMultiDel"` - ForceSigV2 bool `json:"forceSigV2"` - Debug bool `json:"debug"` + Region string `json:"region" yaml:"region"` + Bucket string `json:"bucket" yaml:"bucket"` + Prefix string `json:"prefix" yaml:"prefix"` + ACL string `json:"acl" yaml:"acl"` + AccessKeyID string `json:"awsAccessKeyID" yaml:"access_key_id"` + SecretAccessKey string `json:"awsSecretAccessKey" yaml:"secret_access_key"` + SessionToken string `json:"awsSessionToken" yaml:"session_token"` + Endpoint string `json:"endpoint" yaml:"endpoint"` + StorageClass string `json:"storageClass" yaml:"storage_class"` + EncryptionMethod string `json:"encryptionMethod" yaml:"encryption_method"` + PlusWorkaround bool `json:"plusWorkaround" yaml:"plus_workaround"` + DisableMultiDel bool `json:"disableMultiDel" yaml:"disable_multidel"` + ForceSigV2 bool `json:"forceSigV2" yaml:"force_sigv2"` + ForceVirtualHostedStyle bool `json:"forceVirtualHostedStyle" yaml:"force_virtualhosted_style"` + Debug bool `json:"debug" yaml:"debug"` } // SwiftPublishRoot describes single OpenStack Swift publishing entry point type SwiftPublishRoot struct { - UserName string `json:"osname"` - Password string `json:"password"` - AuthURL string `json:"authurl"` - Tenant string `json:"tenant"` - TenantID string `json:"tenantid"` - Domain string `json:"domain"` - DomainID string `json:"domainid"` - TenantDomain string `json:"tenantdomain"` - TenantDomainID string `json:"tenantdomainid"` - Prefix string `json:"prefix"` - Container string `json:"container"` + Container string `json:"container" yaml:"container"` + Prefix string `json:"prefix" yaml:"prefix"` + UserName string `json:"osname" yaml:"username"` + Password string `json:"password" yaml:"password"` + Tenant string `json:"tenant" yaml:"tenant"` + TenantID string `json:"tenantid" yaml:"tenant_id"` + Domain string `json:"domain" yaml:"domain"` + DomainID string `json:"domainid" yaml:"domain_id"` + TenantDomain string `json:"tenantdomain" yaml:"tenant_domain"` + TenantDomainID string `json:"tenantdomainid" yaml:"tenant_domain_id"` + AuthURL string `json:"authurl" yaml:"auth_url"` } -// AzurePublishRoot describes single Azure publishing entry point -type AzurePublishRoot struct { - AccountName string `json:"accountName"` - AccountKey string `json:"accountKey"` - Container string `json:"container"` - Prefix string `json:"prefix"` - Endpoint string `json:"endpoint"` +// AzureEndpoint describes single Azure publishing entry point +type AzureEndpoint struct { + Container string `json:"container" yaml:"container"` + Prefix string `json:"prefix" yaml:"prefix"` + AccountName string `json:"accountName" yaml:"account_name"` + AccountKey string `json:"accountKey" yaml:"account_key"` + Endpoint string `json:"endpoint" yaml:"endpoint"` } // Config is configuration for aptly, shared by all modules @@ -102,15 +227,22 @@ var Config = ConfigStructure{ GpgDisableSign: false, GpgDisableVerify: false, DownloadSourcePackages: false, + PackagePoolStorage: PackagePoolStorage{ + Local: &LocalPoolStorage{Path: ""}, + }, SkipLegacyPool: false, PpaDistributorID: "ubuntu", PpaCodename: "", FileSystemPublishRoots: map[string]FileSystemPublishRoot{}, S3PublishRoots: map[string]S3PublishRoot{}, SwiftPublishRoots: map[string]SwiftPublishRoot{}, - AzurePublishRoots: map[string]AzurePublishRoot{}, + AzurePublishRoots: map[string]AzureEndpoint{}, AsyncAPI: false, EnableMetricsEndpoint: false, + LogLevel: "info", + LogFormat: "default", + ServeInAPIMode: false, + EnableSwaggerEndpoint: false, } // LoadConfig loads configuration from json file @@ -121,8 +253,17 @@ func LoadConfig(filename string, config *ConfigStructure) error { } defer f.Close() - dec := json.NewDecoder(f) - return dec.Decode(&config) + decJSON := json.NewDecoder(JsonConfigReader.New(f)) + if err = decJSON.Decode(&config); err != nil { + f.Seek(0, 0) + decYAML := yaml.NewDecoder(f) + if err2 := decYAML.Decode(&config); err2 != nil { + err = fmt.Errorf("invalid yaml (%s) or json (%s)", err2, err) + } else { + err = nil + } + } + return err } // SaveConfig write configuration to json file @@ -141,3 +282,37 @@ func SaveConfig(filename string, config *ConfigStructure) error { _, err = f.Write(encoded) return err } + +// SaveConfigRaw write configuration to file +func SaveConfigRaw(filename string, conf []byte) error { + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + _, err = f.Write(conf) + return err +} + +// SaveConfigYAML write configuration to yaml file +func SaveConfigYAML(filename string, config *ConfigStructure) error { + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + yamlData, err := yaml.Marshal(&config) + if err != nil { + return fmt.Errorf("error marshaling to YAML: %s", err) + } + + _, err = f.Write(yamlData) + return err +} + +// GetRootDir returns the RootDir with expanded ~ as home directory +func (conf *ConfigStructure) GetRootDir() string { + return strings.Replace(conf.RootDir, "~", os.Getenv("HOME"), 1) +} diff --git a/utils/config_test.go b/utils/config_test.go index 3dc523c4..66f1ad03 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -14,26 +14,34 @@ type ConfigSuite struct { var _ = Suite(&ConfigSuite{}) func (s *ConfigSuite) TestLoadConfig(c *C) { - configname := filepath.Join(c.MkDir(), "aptly.json") + configname := filepath.Join(c.MkDir(), "aptly.json1") f, _ := os.Create(configname) f.WriteString(configFile) f.Close() + // start with empty config + s.config = ConfigStructure{} + err := LoadConfig(configname, &s.config) c.Assert(err, IsNil) - c.Check(s.config.RootDir, Equals, "/opt/aptly/") + c.Check(s.config.GetRootDir(), Equals, "/opt/aptly/") c.Check(s.config.DownloadConcurrency, Equals, 33) c.Check(s.config.DatabaseOpenAttempts, Equals, 33) } func (s *ConfigSuite) TestSaveConfig(c *C) { - configname := filepath.Join(c.MkDir(), "aptly.json") + configname := filepath.Join(c.MkDir(), "aptly.json2") + + // start with empty config + s.config = ConfigStructure{} s.config.RootDir = "/tmp/aptly" s.config.DownloadConcurrency = 5 s.config.DatabaseOpenAttempts = 5 s.config.GpgProvider = "gpg" + s.config.PackagePoolStorage.Local = &LocalPoolStorage{"/tmp/aptly-pool"} + s.config.FileSystemPublishRoots = map[string]FileSystemPublishRoot{"test": { RootDir: "/opt/aptly-publish"}} @@ -44,9 +52,12 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { s.config.SwiftPublishRoots = map[string]SwiftPublishRoot{"test": { Container: "repo"}} - s.config.AzurePublishRoots = map[string]AzurePublishRoot{"test": { + s.config.AzurePublishRoots = map[string]AzureEndpoint{"test": { Container: "repo"}} + s.config.LogLevel = "info" + s.config.LogFormat = "json" + err := SaveConfig(configname, &s.config) c.Assert(err, IsNil) @@ -58,80 +69,305 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { f.Read(buf) c.Check(string(buf), Equals, ""+ - "{\n"+ - " \"rootDir\": \"/tmp/aptly\",\n"+ - " \"downloadConcurrency\": 5,\n"+ - " \"downloadSpeedLimit\": 0,\n"+ - " \"downloadRetries\": 0,\n"+ - " \"downloader\": \"\",\n"+ - " \"databaseOpenAttempts\": 5,\n"+ - " \"architectures\": null,\n"+ - " \"dependencyFollowSuggests\": false,\n"+ - " \"dependencyFollowRecommends\": false,\n"+ - " \"dependencyFollowAllVariants\": false,\n"+ - " \"dependencyFollowSource\": false,\n"+ - " \"dependencyVerboseResolve\": false,\n"+ - " \"gpgDisableSign\": false,\n"+ - " \"gpgDisableVerify\": false,\n"+ - " \"gpgProvider\": \"gpg\",\n"+ - " \"downloadSourcePackages\": false,\n"+ - " \"skipLegacyPool\": false,\n"+ - " \"ppaDistributorID\": \"\",\n"+ - " \"ppaCodename\": \"\",\n"+ - " \"skipContentsPublishing\": false,\n"+ - " \"skipBz2Publishing\": false,\n"+ - " \"FileSystemPublishEndpoints\": {\n"+ - " \"test\": {\n"+ - " \"rootDir\": \"/opt/aptly-publish\",\n"+ - " \"linkMethod\": \"\",\n"+ - " \"verifyMethod\": \"\"\n"+ - " }\n"+ - " },\n"+ - " \"S3PublishEndpoints\": {\n"+ - " \"test\": {\n"+ - " \"region\": \"us-east-1\",\n"+ - " \"bucket\": \"repo\",\n"+ - " \"endpoint\": \"\",\n"+ - " \"awsAccessKeyID\": \"\",\n"+ - " \"awsSecretAccessKey\": \"\",\n"+ - " \"awsSessionToken\": \"\",\n"+ - " \"prefix\": \"\",\n"+ - " \"acl\": \"\",\n"+ - " \"storageClass\": \"\",\n"+ - " \"encryptionMethod\": \"\",\n"+ - " \"plusWorkaround\": false,\n"+ - " \"disableMultiDel\": false,\n"+ - " \"forceSigV2\": false,\n"+ - " \"debug\": false\n"+ - " }\n"+ - " },\n"+ - " \"SwiftPublishEndpoints\": {\n"+ - " \"test\": {\n"+ - " \"osname\": \"\",\n"+ - " \"password\": \"\",\n"+ - " \"authurl\": \"\",\n"+ - " \"tenant\": \"\",\n"+ - " \"tenantid\": \"\",\n"+ - " \"domain\": \"\",\n"+ - " \"domainid\": \"\",\n"+ - " \"tenantdomain\": \"\",\n"+ - " \"tenantdomainid\": \"\",\n"+ - " \"prefix\": \"\",\n"+ - " \"container\": \"repo\"\n"+ - " }\n"+ - " },\n"+ - " \"AzurePublishEndpoints\": {\n"+ - " \"test\": {\n"+ - " \"accountName\": \"\",\n"+ - " \"accountKey\": \"\",\n"+ - " \"container\": \"repo\",\n"+ - " \"prefix\": \"\",\n"+ - " \"endpoint\": \"\"\n"+ - " }\n"+ - " },\n"+ - " \"AsyncAPI\": false,\n"+ - " \"enableMetricsEndpoint\": false\n"+ + "{\n" + + " \"rootDir\": \"/tmp/aptly\",\n" + + " \"logLevel\": \"info\",\n" + + " \"logFormat\": \"json\",\n" + + " \"databaseOpenAttempts\": 5,\n" + + " \"architectures\": null,\n" + + " \"skipLegacyPool\": false,\n" + + " \"dependencyFollowSuggests\": false,\n" + + " \"dependencyFollowRecommends\": false,\n" + + " \"dependencyFollowAllVariants\": false,\n" + + " \"dependencyFollowSource\": false,\n" + + " \"dependencyVerboseResolve\": false,\n" + + " \"ppaDistributorID\": \"\",\n" + + " \"ppaCodename\": \"\",\n" + + " \"serveInAPIMode\": false,\n" + + " \"enableMetricsEndpoint\": false,\n" + + " \"enableSwaggerEndpoint\": false,\n" + + " \"AsyncAPI\": false,\n" + + " \"databaseBackend\": {\n" + + " \"type\": \"\",\n" + + " \"dbPath\": \"\",\n" + + " \"url\": \"\"\n" + + " },\n" + + " \"downloader\": \"\",\n" + + " \"downloadConcurrency\": 5,\n" + + " \"downloadSpeedLimit\": 0,\n" + + " \"downloadRetries\": 0,\n" + + " \"downloadSourcePackages\": false,\n" + + " \"gpgProvider\": \"gpg\",\n" + + " \"gpgDisableSign\": false,\n" + + " \"gpgDisableVerify\": false,\n" + + " \"skipContentsPublishing\": false,\n" + + " \"skipBz2Publishing\": false,\n" + + " \"FileSystemPublishEndpoints\": {\n" + + " \"test\": {\n" + + " \"rootDir\": \"/opt/aptly-publish\",\n" + + " \"linkMethod\": \"\",\n" + + " \"verifyMethod\": \"\"\n" + + " }\n" + + " },\n" + + " \"S3PublishEndpoints\": {\n" + + " \"test\": {\n" + + " \"region\": \"us-east-1\",\n" + + " \"bucket\": \"repo\",\n" + + " \"prefix\": \"\",\n" + + " \"acl\": \"\",\n" + + " \"awsAccessKeyID\": \"\",\n" + + " \"awsSecretAccessKey\": \"\",\n" + + " \"awsSessionToken\": \"\",\n" + + " \"endpoint\": \"\",\n" + + " \"storageClass\": \"\",\n" + + " \"encryptionMethod\": \"\",\n" + + " \"plusWorkaround\": false,\n" + + " \"disableMultiDel\": false,\n" + + " \"forceSigV2\": false,\n" + + " \"forceVirtualHostedStyle\": false,\n" + + " \"debug\": false\n" + + " }\n" + + " },\n" + + " \"SwiftPublishEndpoints\": {\n" + + " \"test\": {\n" + + " \"container\": \"repo\",\n" + + " \"prefix\": \"\",\n" + + " \"osname\": \"\",\n" + + " \"password\": \"\",\n" + + " \"tenant\": \"\",\n" + + " \"tenantid\": \"\",\n" + + " \"domain\": \"\",\n" + + " \"domainid\": \"\",\n" + + " \"tenantdomain\": \"\",\n" + + " \"tenantdomainid\": \"\",\n" + + " \"authurl\": \"\"\n" + + " }\n" + + " },\n" + + " \"AzurePublishEndpoints\": {\n" + + " \"test\": {\n" + + " \"container\": \"repo\",\n" + + " \"prefix\": \"\",\n" + + " \"accountName\": \"\",\n" + + " \"accountKey\": \"\",\n" + + " \"endpoint\": \"\"\n" + + " }\n" + + " },\n" + + " \"packagePoolStorage\": {\n" + + " \"type\": \"local\",\n" + + " \"path\": \"/tmp/aptly-pool\"\n" + + " }\n" + "}") } +func (s *ConfigSuite) TestLoadYAMLConfig(c *C) { + configname := filepath.Join(c.MkDir(), "aptly.yaml1") + f, _ := os.Create(configname) + f.WriteString(configFileYAML) + f.Close() + + // start with empty config + s.config = ConfigStructure{} + + err := LoadConfig(configname, &s.config) + c.Assert(err, IsNil) + c.Check(s.config.GetRootDir(), Equals, "/opt/aptly/") + c.Check(s.config.DownloadConcurrency, Equals, 40) + c.Check(s.config.DatabaseOpenAttempts, Equals, 10) +} + +func (s *ConfigSuite) TestLoadYAMLErrorConfig(c *C) { + configname := filepath.Join(c.MkDir(), "aptly.yaml2") + f, _ := os.Create(configname) + f.WriteString(configFileYAMLError) + f.Close() + + // start with empty config + s.config = ConfigStructure{} + + err := LoadConfig(configname, &s.config) + c.Assert(err.Error(), Equals, "invalid yaml (unknown pool storage type: invalid) or json (invalid character 'p' looking for beginning of value)") +} + +func (s *ConfigSuite) TestSaveYAMLConfig(c *C) { + configname := filepath.Join(c.MkDir(), "aptly.yaml3") + f, _ := os.Create(configname) + f.WriteString(configFileYAML) + f.Close() + + // start with empty config + s.config = ConfigStructure{} + + err := LoadConfig(configname, &s.config) + c.Assert(err, IsNil) + + err = SaveConfigYAML(configname, &s.config) + c.Assert(err, IsNil) + + f, _ = os.Open(configname) + defer f.Close() + + st, _ := f.Stat() + buf := make([]byte, st.Size()) + f.Read(buf) + + c.Check(string(buf), Equals, configFileYAML) +} + +func (s *ConfigSuite) TestSaveYAML2Config(c *C) { + // start with empty config + s.config = ConfigStructure{} + + s.config.PackagePoolStorage.Local = &LocalPoolStorage{"/tmp/aptly-pool"} + s.config.PackagePoolStorage.Azure = nil + + configname := filepath.Join(c.MkDir(), "aptly.yaml4") + err := SaveConfigYAML(configname, &s.config) + c.Assert(err, IsNil) + + f, _ := os.Open(configname) + defer f.Close() + + st, _ := f.Stat() + buf := make([]byte, st.Size()) + f.Read(buf) + + c.Check(string(buf), Equals, "" + + "root_dir: \"\"\n" + + "log_level: \"\"\n" + + "log_format: \"\"\n" + + "database_open_attempts: 0\n" + + "architectures: []\n" + + "skip_legacy_pool: false\n" + + "dep_follow_suggests: false\n" + + "dep_follow_recommends: false\n" + + "dep_follow_all_variants: false\n" + + "dep_follow_source: false\n" + + "dep_verboseresolve: false\n" + + "ppa_distributor_id: \"\"\n" + + "ppa_codename: \"\"\n" + + "serve_in_api_mode: false\n" + + "enable_metrics_endpoint: false\n" + + "enable_swagger_endpoint: false\n" + + "async_api: false\n" + + "database_backend:\n" + + " type: \"\"\n" + + " db_path: \"\"\n" + + " url: \"\"\n" + + "downloader: \"\"\n" + + "download_concurrency: 0\n" + + "download_limit: 0\n" + + "download_retries: 0\n" + + "download_sourcepackages: false\n" + + "gpg_provider: \"\"\n" + + "gpg_disable_sign: false\n" + + "gpg_disable_verify: false\n" + + "skip_contents_publishing: false\n" + + "skip_bz2_publishing: false\n" + + "filesystem_publish_endpoints: {}\n" + + "s3_publish_endpoints: {}\n" + + "swift_publish_endpoints: {}\n" + + "azure_publish_endpoints: {}\n" + + "packagepool_storage:\n" + + " type: local\n" + + " path: /tmp/aptly-pool\n") +} + +func (s *ConfigSuite) TestLoadEmptyConfig(c *C) { + configname := filepath.Join(c.MkDir(), "aptly.yaml5") + f, _ := os.Create(configname) + f.Close() + + // start with empty config + s.config = ConfigStructure{} + + err := LoadConfig(configname, &s.config) + c.Assert(err.Error(), Equals, "invalid yaml (EOF) or json (EOF)") +} + const configFile = `{"rootDir": "/opt/aptly/", "downloadConcurrency": 33, "databaseOpenAttempts": 33}` +const configFileYAML = `root_dir: /opt/aptly/ +log_level: error +log_format: json +database_open_attempts: 10 +architectures: + - amd64 + - arm64 +skip_legacy_pool: true +dep_follow_suggests: true +dep_follow_recommends: true +dep_follow_all_variants: true +dep_follow_source: true +dep_verboseresolve: true +ppa_distributor_id: Ubuntu +ppa_codename: code +serve_in_api_mode: true +enable_metrics_endpoint: true +enable_swagger_endpoint: true +async_api: true +database_backend: + type: etcd + db_path: "" + url: 127.0.0.1:2379 +downloader: grab +download_concurrency: 40 +download_limit: 100 +download_retries: 10 +download_sourcepackages: true +gpg_provider: gpg +gpg_disable_sign: true +gpg_disable_verify: true +skip_contents_publishing: true +skip_bz2_publishing: true +filesystem_publish_endpoints: + test1: + root_dir: /opt/srv/aptly_public + link_method: hardlink + verify_method: md5 +s3_publish_endpoints: + test: + region: us-east-1 + bucket: test-bucket + prefix: prfx + acl: public-read + access_key_id: "2" + secret_access_key: secret + session_token: none + endpoint: endpoint + storage_class: STANDARD + encryption_method: AES256 + plus_workaround: true + disable_multidel: true + force_sigv2: true + force_virtualhosted_style: true + debug: true +swift_publish_endpoints: + test: + container: c1 + prefix: pre + username: user + password: pass + tenant: t + tenant_id: "2" + domain: pop + domain_id: "1" + tenant_domain: td + tenant_domain_id: "3" + auth_url: http://auth.url +azure_publish_endpoints: + test: + container: container1 + prefix: pre2 + account_name: aname + account_key: akey + endpoint: https://end.point +packagepool_storage: + type: azure + container: test-pool1 + prefix: pre3 + account_name: a name + account_key: a key + endpoint: ep +` +const configFileYAMLError = `packagepool_storage: + type: invalid +` diff --git a/utils/copyfile_test.go b/utils/copyfile_test.go new file mode 100644 index 00000000..cd2b4446 --- /dev/null +++ b/utils/copyfile_test.go @@ -0,0 +1,28 @@ +package utils + +import ( + "os" + "path/filepath" + + . "gopkg.in/check.v1" +) + +type CopyfileSuite struct { + source *os.File + dest string +} + +var _ = Suite(&CopyfileSuite{}) + +func (s *CopyfileSuite) SetUpSuite(c *C) { + s.source, _ = os.CreateTemp(c.MkDir(), "source-file") + s.dest = filepath.Join(filepath.Dir(s.source.Name()), "destination-file") +} + +func (s *CopyfileSuite) TestCopyFile(c *C) { + err := CopyFile(s.source.Name(), s.dest) + c.Check(err, Equals, nil) + + _, err = os.Stat(s.dest) + c.Check(err, Equals, nil) +} diff --git a/utils/logging.go b/utils/logging.go new file mode 100644 index 00000000..592e8f30 --- /dev/null +++ b/utils/logging.go @@ -0,0 +1,65 @@ +package utils + +import ( + "io" + "os" + "strings" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +type LogWriter struct { + Logger zerolog.Logger +} + +func (lw LogWriter) Write(bs []byte) (int, error) { + return lw.Logger.With().Str("level", "info").Logger().Write(bs) +} + +func SetupJSONLogger(levelStr string, w io.Writer) { + zerolog.MessageFieldName = "message" + zerolog.LevelFieldName = "level" + + var tsHook timestampHook + log.Logger = zerolog.New(w). + Hook(&tsHook). + Level(GetLogLevelOrDebug(levelStr)) +} + +func SetupDefaultLogger(levelStr string) { + zerolog.MessageFieldName = "message" + zerolog.LevelFieldName = "level" + + log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}). + Level(GetLogLevelOrDebug(levelStr)). + With(). + Timestamp(). + Logger() +} + +func GetLogLevelOrDebug(levelStr string) zerolog.Level { + levelStr = strings.ToLower(levelStr) + if levelStr == "warning" { + levelStr = "warn" + } + + var level zerolog.Level + + err := level.UnmarshalText([]byte(levelStr)) + if err == nil { + return level + } + + log.Warn().Msgf("Unknown log level '%s', defaulting to debug", levelStr) + return zerolog.DebugLevel +} + +type timestampHook struct{} + +func (h *timestampHook) Run(e *zerolog.Event, _ zerolog.Level, _ string) { + t := time.Now() + ts := t.Format(time.RFC3339) + e.Str("time", ts) +} diff --git a/utils/utils.go b/utils/utils.go index 598dc7c8..eb9677d3 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,21 +4,31 @@ package utils import ( "fmt" "os" + "strings" "golang.org/x/sys/unix" ) // DirIsAccessible verifies that directory exists and is accessible func DirIsAccessible(filename string) error { - _, err := os.Stat(filename) + fileStat, err := os.Stat(filename) if err != nil { if !os.IsNotExist(err) { return fmt.Errorf("error checking directory '%s': %s", filename, err) } } else { - if unix.Access(filename, unix.W_OK) != nil { + if fileStat.Mode().Perm() == 0000 || unix.Access(filename, unix.W_OK) != nil { return fmt.Errorf("'%s' is inaccessible, check access rights", filename) } } return nil } + +// Remove leading '/', remove '..', '$' and '`' +func SanitizePath(path string) (result string) { + result = strings.Replace(path, "..", "", -1) + result = strings.Replace(result, "$", "", -1) + result = strings.Replace(result, "`", "", -1) + result = strings.TrimLeft(result, "/") + return +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 65cd6706..17c46f13 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1,6 +1,9 @@ package utils import ( + "fmt" + "log" + "os" "testing" . "gopkg.in/check.v1" @@ -10,3 +13,28 @@ import ( func Test(t *testing.T) { TestingT(t) } + +type UtilsSuite struct { + tempfile *os.File +} + +var _ = Suite(&UtilsSuite{}) + +func (s *UtilsSuite) SetUpSuite(c *C) { + s.tempfile, _ = os.CreateTemp(c.MkDir(), "aptly-test-inaccessible") + if err := os.Chmod(s.tempfile.Name(), 0000); err != nil { + log.Fatalln(err) + } +} + +func (s *UtilsSuite) TestDirIsAccessibleNotExist(c *C) { + c.Check(DirIsAccessible("does/not/exist.invalid"), Equals, nil) +} + +func (s *UtilsSuite) TestDirIsAccessibleNotAccessible(c *C) { + accessible := DirIsAccessible(s.tempfile.Name()) + if accessible == nil { + c.Fatalf("Test dir should not be accessible: %s", s.tempfile.Name()) + } + c.Check(accessible.Error(), Equals, fmt.Errorf("'%s' is inaccessible, check access rights", s.tempfile.Name()).Error()) +}