mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-31 04:30:44 +00:00
Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68814ff1f0 | |||
| d44ae522ac | |||
| 8f2b335409 | |||
| 9ecbc844e7 | |||
| 9e91ee4c4a | |||
| b7969c7a2d | |||
| 2a5992c74e | |||
| 2827620cfe | |||
| 8dc61cf362 | |||
| 4a9ddbdc34 | |||
| c316ea9b73 | |||
| d027a251ba | |||
| 16b6348710 | |||
| 1c1abe6b10 | |||
| c4bfbe52ca | |||
| c723fea807 | |||
| 0d31298f37 | |||
| bba6bd7db5 | |||
| faeaad0378 | |||
| a20eb6866a | |||
| 809ab47042 | |||
| 0b84009b4a | |||
| 92d7561d49 | |||
| e908531bef | |||
| f8620d10b2 | |||
| 8be72b48a1 | |||
| 5655480e00 | |||
| 3c8defa304 | |||
| 1ed50697ec | |||
| 3b432d42b5 | |||
| 89e3bdfa07 | |||
| f8d2d3cb8d | |||
| 01004e19c0 | |||
| 92bb28149c | |||
| 652210acfa | |||
| 45f3da256b | |||
| 3c5e83366a | |||
| a7a4bb7001 | |||
| 2f7f726d4c | |||
| 43d7284657 | |||
| 02423af931 | |||
| 2a228625e2 | |||
| 16e0302f30 | |||
| 6ecbc9ba90 | |||
| 7276b9621f | |||
| fb7734b5b0 | |||
| 29c37293b9 | |||
| f25ba2e6b0 | |||
| 6a5b9ddacf | |||
| 48355f65ed | |||
| d616977904 | |||
| 3c068febde | |||
| 76adbe49e0 | |||
| f6221a2413 | |||
| 4f46cb04f5 | |||
| 66e814c086 | |||
| b3f5d96490 | |||
| 144265122a | |||
| 4f95d75c37 | |||
| 8db1d2e7f1 | |||
| 4088a811cd | |||
| 2ac4c75fad | |||
| e2ebcbb02a | |||
| 9defe70190 | |||
| 23943d47e9 | |||
| 49f342878a | |||
| 1f29c65a95 | |||
| a65f79eb79 | |||
| c6a9f82358 | |||
| 1702537979 | |||
| 12604b9379 | |||
| 9b523e6bd5 | |||
| aa030e3f36 | |||
| 8e739524b0 | |||
| 4ba4c0cba6 | |||
| 48d02918c1 | |||
| d672bfa317 | |||
| 9a90038dd2 | |||
| 7d23196f76 | |||
| 2c6812934e | |||
| 60f3eb151b | |||
| 0db9797c4e | |||
| a2ffffedc1 | |||
| 19b98c62c1 | |||
| 06fea598e1 | |||
| 9a6f06d23e | |||
| 67f6a0e458 | |||
| a57e2aecd7 | |||
| 1e7c15b69b | |||
| a70f572efd | |||
| 8c183b45c6 | |||
| a8f7f58dab | |||
| 31fe26de5e | |||
| eb1b770dc2 | |||
| abb2ad635f | |||
| a75df0a697 | |||
| ea797f8ebe | |||
| a4cc9211d6 | |||
| 836d9f3b8b | |||
| 61650e5b3b | |||
| 4e457aa570 | |||
| fe70da9c08 | |||
| 4b57e65658 | |||
| bcd81eeae4 | |||
| af483d1165 | |||
| 6b8651fda2 | |||
| de699aebe5 | |||
| 0021cf876b | |||
| 32b601bde6 | |||
| b464e7f80b | |||
| e0f282aca9 | |||
| ba65daf6cb | |||
| b8455f6de9 | |||
| 132c923f25 | |||
| b6d83a4f61 | |||
| 4526d6d831 | |||
| 02d2ba255c | |||
| d94792dd65 | |||
| 66eb75f492 | |||
| 33a2f70d07 | |||
| 10f942c8e0 | |||
| 568a9ce4d5 | |||
| ddf415a359 | |||
| 29ac9c1919 | |||
| d3bed7830c | |||
| c2d5f47643 | |||
| 731e92c8e4 | |||
| 94a600c0c1 | |||
| e1d8ae8a35 | |||
| d3b7186dea | |||
| 3608c137a0 | |||
| 15a3efe758 | |||
| 4b73ae462f | |||
| b49a631e0b | |||
| 12b6b04055 | |||
| a1f659bea0 | |||
| 8ca4cb8dcb | |||
| 8ce8f250d5 | |||
| 3672f6f92f | |||
| 888a6b2caa | |||
| 231039e86c | |||
| dc884e6052 | |||
| 4675589cf6 | |||
| 32f03bfd62 | |||
| d1bfd29dfd | |||
| 27ec594606 | |||
| f652a522fd | |||
| a794e87490 | |||
| 5b04d4fbe1 | |||
| 1566e193f6 | |||
| 601c8e9d52 | |||
| 8e5707dbcc | |||
| ad4d0c7b96 | |||
| a11e004943 | |||
| f605d86a4e | |||
| f8bde63081 | |||
| 887ce71005 | |||
| 87233ceafe | |||
| 27c15680e8 | |||
| cb72e2d70f | |||
| 2cafbc8484 | |||
| 6dbb28b2b8 | |||
| d8a4a28259 | |||
| 9a217171c8 | |||
| c67cafcf94 | |||
| f7057a9517 | |||
| ae5379d84a | |||
| c05068c2e8 | |||
| 22bc2f9d0f | |||
| c07bf2b108 | |||
| e447fc0f1e | |||
| e062df68c5 | |||
| 664a5cd675 | |||
| 9ef217b351 | |||
| 67bd15487d |
@@ -4,6 +4,10 @@ Fixes #
|
|||||||
|
|
||||||
All new code should be covered with tests, documentation should be updated. CI should pass.
|
All new code should be covered with tests, documentation should be updated. CI should pass.
|
||||||
|
|
||||||
|
Also, to speed up things, if you could kindly "Allow edits and access to secrets by maintainers" in the
|
||||||
|
PR settings, as this allows us to rebase the PR on master, fix conflicts, run coverage and help with
|
||||||
|
implementing code and tests.
|
||||||
|
|
||||||
## Description of the Change
|
## Description of the Change
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -14,6 +18,7 @@ Why this change is important?
|
|||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] allow Maintainers to edit PR (rebase, run coverage, help with tests, ...)
|
||||||
- [ ] unit-test added (if change is algorithm)
|
- [ ] unit-test added (if change is algorithm)
|
||||||
- [ ] functional test added/updated (if change is functional)
|
- [ ] functional test added/updated (if change is functional)
|
||||||
- [ ] man page updated (if applicable)
|
- [ ] man page updated (if applicable)
|
||||||
|
|||||||
+102
-28
@@ -17,8 +17,32 @@ env:
|
|||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
unit-test:
|
||||||
|
name: "Unit Tests"
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
continue-on-error: false
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- name: "Checkout Repository"
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# fetch the whole repo for `git describe` to work
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: "Docker Image"
|
||||||
|
run: |
|
||||||
|
make docker-image
|
||||||
|
- name: "Unit Tests"
|
||||||
|
run: |
|
||||||
|
make docker-unit-tests
|
||||||
|
mkdir -p out/coverage
|
||||||
|
mv unit.out out/coverage/
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: unit-tests-coverage
|
||||||
|
path: out/
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: "Test (Ubuntu 22.04)"
|
name: "System Test"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
@@ -30,10 +54,10 @@ jobs:
|
|||||||
GOPROXY: "https://proxy.golang.org"
|
GOPROXY: "https://proxy.golang.org"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Install Packages"
|
- name: "Install Test Packages"
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
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
|
sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8 faketime
|
||||||
|
|
||||||
- name: "Checkout Repository"
|
- name: "Checkout Repository"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -63,21 +87,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
directory: ${{ runner.temp }}
|
directory: ${{ runner.temp }}
|
||||||
|
|
||||||
- name: "Run Unit 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 test
|
|
||||||
|
|
||||||
- name: "Run Benchmark"
|
- name: "Run Benchmark"
|
||||||
run: |
|
run: |
|
||||||
COVERAGE_DIR=${{ runner.temp }} make bench
|
mkdir -p out/coverage
|
||||||
|
COVERAGE_DIR=$PWD/out/coverage make bench
|
||||||
|
|
||||||
- name: "Run System Tests"
|
- name: "Run System Tests"
|
||||||
env:
|
env:
|
||||||
@@ -89,30 +102,63 @@ jobs:
|
|||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
run: |
|
run: |
|
||||||
sudo mkdir -p /srv ; sudo chown runner /srv
|
sudo mkdir -p /srv ; sudo chown runner /srv
|
||||||
COVERAGE_DIR=${{ runner.temp }} make system-test
|
mkdir -p out/coverage
|
||||||
|
COVERAGE_DIR=$PWD/out/coverage make system-test
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: system-tests-coverage
|
||||||
|
path: out/
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: "Upload Coverage"
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
continue-on-error: false
|
||||||
|
timeout-minutes: 30
|
||||||
|
needs:
|
||||||
|
- unit-test
|
||||||
|
- test
|
||||||
|
steps:
|
||||||
|
- name: "Checkout Repository"
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: "Download Unit Test Coverage"
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: unit-tests-coverage
|
||||||
|
|
||||||
|
- name: "Download System Test Coverage"
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: system-tests-coverage
|
||||||
|
|
||||||
- name: "Merge Code Coverage"
|
- name: "Merge Code Coverage"
|
||||||
run: |
|
run: |
|
||||||
go install github.com/wadey/gocovmerge@latest
|
# go install github.com/wadey/gocovmerge@v0.0.0-20160331181800-b5bfa59ec0ad
|
||||||
~/go/bin/gocovmerge unit.out ${{ runner.temp }}/*.out > coverage.txt
|
# ~/go/bin/gocovmerge coverage/*.out > coverage.txt
|
||||||
|
awk 'FNR==1 && NR!=1 {next} {print}' coverage/*.out > coverage.txt
|
||||||
|
|
||||||
- name: "Upload Code Coverage"
|
- name: "Upload Code Coverage"
|
||||||
uses: codecov/codecov-action@v2
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: coverage.txt
|
files: coverage.txt
|
||||||
|
fail_ci_if_error: true
|
||||||
|
|
||||||
|
|
||||||
ci-debian-build:
|
ci-debian-build:
|
||||||
name: "Build"
|
name: "Build"
|
||||||
needs: test
|
needs:
|
||||||
|
- coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
name: ["Debian 13/testing", "Debian 12/bookworm", "Debian 11/bullseye", "Debian 10/buster", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
name: ["Debian 13/trixie", "Debian 12/bookworm", "Debian 11/bullseye", "Ubuntu 26.04", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
||||||
arch: ["amd64", "i386" , "arm64" , "armhf"]
|
arch: ["amd64", "i386" , "arm64" , "armhf"]
|
||||||
include:
|
include:
|
||||||
- name: "Debian 13/testing"
|
- name: "Debian 13/trixie"
|
||||||
suite: trixie
|
suite: trixie
|
||||||
image: debian:trixie-slim
|
image: debian:trixie-slim
|
||||||
- name: "Debian 12/bookworm"
|
- name: "Debian 12/bookworm"
|
||||||
@@ -121,9 +167,9 @@ jobs:
|
|||||||
- name: "Debian 11/bullseye"
|
- name: "Debian 11/bullseye"
|
||||||
suite: bullseye
|
suite: bullseye
|
||||||
image: debian:bullseye-slim
|
image: debian:bullseye-slim
|
||||||
- name: "Debian 10/buster"
|
- name: "Ubuntu 26.04"
|
||||||
suite: buster
|
suite: resolute
|
||||||
image: debian:buster-slim
|
image: ubuntu:26.04
|
||||||
- name: "Ubuntu 24.04"
|
- name: "Ubuntu 24.04"
|
||||||
suite: noble
|
suite: noble
|
||||||
image: ubuntu:24.04
|
image: ubuntu:24.04
|
||||||
@@ -135,11 +181,12 @@ jobs:
|
|||||||
image: ubuntu:20.04
|
image: ubuntu:20.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.image }}
|
image: ${{ matrix.image }}
|
||||||
|
options: --user root
|
||||||
env:
|
env:
|
||||||
APT_LISTCHANGES_FRONTEND: none
|
APT_LISTCHANGES_FRONTEND: none
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
steps:
|
steps:
|
||||||
- name: "Install packages"
|
- name: "Install Build Packages"
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang jq bash-completion lintian \
|
apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang jq bash-completion lintian \
|
||||||
@@ -207,9 +254,27 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
.github/workflows/scripts/upload-artifacts.sh release ${{ matrix.suite }}
|
.github/workflows/scripts/upload-artifacts.sh release ${{ matrix.suite }}
|
||||||
|
|
||||||
|
- 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: "Upload CI Artifacts"
|
||||||
|
if: github.ref != 'refs/heads/master' && !startsWith(github.event.ref, 'refs/tags')
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.suite }}_${{ matrix.arch }}
|
||||||
|
path: build/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
ci-binary-build:
|
ci-binary-build:
|
||||||
name: "Build"
|
name: "Build"
|
||||||
needs: test
|
needs:
|
||||||
|
- coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -267,6 +332,15 @@ jobs:
|
|||||||
path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
|
path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
|
||||||
compression-level: 0 # no compression
|
compression-level: 0 # no compression
|
||||||
|
|
||||||
|
- name: "Upload CI Artifacts"
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: "!startsWith(github.event.ref, 'refs/tags')"
|
||||||
|
with:
|
||||||
|
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
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
gh-release:
|
gh-release:
|
||||||
name: "Github Release"
|
name: "Github Release"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install and initialize swagger
|
- name: Install and initialize swagger
|
||||||
run: |
|
run: |
|
||||||
go install github.com/swaggo/swag/cmd/swag@latest
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
swag init -q --markdownFiles docs
|
swag init -q --propertyStrategy pascalcase --markdownFiles docs
|
||||||
shell: sh
|
shell: sh
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
# Require: The version of golangci-lint to use.
|
# 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 `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.
|
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
|
||||||
version: v1.54.1
|
version: v1.64.5
|
||||||
|
|
||||||
# Optional: working directory, useful for monorepos
|
# Optional: working directory, useful for monorepos
|
||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ _testmain.go
|
|||||||
*.test
|
*.test
|
||||||
|
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
coverage.out
|
||||||
|
coverage.html
|
||||||
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
|||||||
+10
-15
@@ -1,16 +1,11 @@
|
|||||||
run:
|
version: "2"
|
||||||
tests: false
|
|
||||||
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
settings:
|
||||||
enable:
|
staticcheck:
|
||||||
- goconst
|
checks:
|
||||||
- gofmt
|
- "all"
|
||||||
- goimports
|
- "-QF1004" # could use strings.ReplaceAll instead
|
||||||
- govet
|
- "-QF1012" # Use fmt.Fprintf(...) instead of WriteString(fmt.Sprintf(...))
|
||||||
- ineffassign
|
- "-QF1003" # could use tagged switch
|
||||||
- misspell
|
- "-ST1000" # at least one file in a package should have a package comment
|
||||||
- revive
|
- "-QF1001" # could apply De Morgan's law
|
||||||
- staticcheck
|
|
||||||
- vetshadow
|
|
||||||
|
|||||||
@@ -68,3 +68,19 @@ List of contributors, in chronological order:
|
|||||||
* Blake Kostner (https://github.com/btkostner)
|
* Blake Kostner (https://github.com/btkostner)
|
||||||
* Leigh London (https://github.com/leighlondon)
|
* Leigh London (https://github.com/leighlondon)
|
||||||
* Gordian Schoenherr (https://github.com/schoenherrg)
|
* Gordian Schoenherr (https://github.com/schoenherrg)
|
||||||
|
* Silke Hofstra (https://github.com/silkeh)
|
||||||
|
* Itay Porezky (https://github.com/itayporezky)
|
||||||
|
* Alejandro Guijarro Monerris (https://github.com/alguimodd)
|
||||||
|
* JupiterRider (https://github.com/JupiterRider)
|
||||||
|
* Agustin Henze (https://github.com/agustinhenze)
|
||||||
|
* Tobias Assarsson (https://github.com/daedaluz)
|
||||||
|
* Yaksh Bariya (https://github.com/thunder-coding)
|
||||||
|
* Juan Calderon-Perez (https://github.com/gaby)
|
||||||
|
* Ato Araki (https://github.com/atotto)
|
||||||
|
* Roman Lebedev (https://github.com/LebedevRI)
|
||||||
|
* Brian Witt (https://github.com/bwitt)
|
||||||
|
* Ales Bregar (https://github.com/abregar)
|
||||||
|
* Tim Foerster (https://github.com/tonobo)
|
||||||
|
* Zhang Xiao (https://github.com/xzhang1)
|
||||||
|
* Tom Nguyen (https://github.com/lecafard)
|
||||||
|
* Philip Cramer (https://github.com/PhilipCramer)
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@ Please report unacceptable behavior on [https://github.com/aptly-dev/aptly/discu
|
|||||||
### List of Repositories
|
### List of Repositories
|
||||||
|
|
||||||
* [aptly-dev/aptly](https://github.com/aptly-dev/aptly) - aptly source code, functional tests, man page
|
* [aptly-dev/aptly](https://github.com/aptly-dev/aptly) - aptly source code, functional tests, man page
|
||||||
* [apty-dev/aptly-dev.github.io](https://github.com/aptly-dev/aptly-dev.github.io) - aptly website (https://www.aptly.info/)
|
* [aptly-dev/aptly-dev.github.io](https://github.com/aptly-dev/aptly-dev.github.io) - aptly website (https://www.aptly.info/)
|
||||||
* [aptly-dev/aptly-fixture-db](https://github.com/aptly-dev/aptly-fixture-db) & [aptly-dev/aptly-fixture-pool](https://github.com/aptly-dev/aptly-fixture-pool) provide
|
* [aptly-dev/aptly-fixture-db](https://github.com/aptly-dev/aptly-fixture-db) & [aptly-dev/aptly-fixture-pool](https://github.com/aptly-dev/aptly-fixture-pool) provide
|
||||||
fixtures for aptly functional tests
|
fixtures for aptly functional tests
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ make docker-unit-tests
|
|||||||
|
|
||||||
In order to run aptly system tests, enter the following:
|
In order to run aptly system tests, enter the following:
|
||||||
```
|
```
|
||||||
make docker-system-tests
|
make docker-system-test
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Running golangci-lint
|
#### Running golangci-lint
|
||||||
|
|||||||
@@ -2,13 +2,32 @@ GOPATH=$(shell go env GOPATH)
|
|||||||
VERSION=$(shell make -s version)
|
VERSION=$(shell make -s version)
|
||||||
PYTHON?=python3
|
PYTHON?=python3
|
||||||
BINPATH?=$(GOPATH)/bin
|
BINPATH?=$(GOPATH)/bin
|
||||||
GOLANGCI_LINT_VERSION=v1.54.1 # version supporting go 1.19
|
GOLANGCI_LINT_VERSION=v2.0.2 # version supporting go 1.24
|
||||||
COVERAGE_DIR?=$(shell mktemp -d)
|
COVERAGE_DIR?=$(shell mktemp -d)
|
||||||
GOOS=$(shell go env GOHOSTOS)
|
GOOS=$(shell go env GOHOSTOS)
|
||||||
GOARCH=$(shell go env GOHOSTARCH)
|
GOARCH=$(shell go env GOHOSTARCH)
|
||||||
|
|
||||||
# Uncomment to update system test gold files
|
export PODMAN_USERNS = keep-id
|
||||||
# CAPTURE := "--capture"
|
DOCKER_RUN = docker run --security-opt label=disable --user 0:0 --rm -v ${PWD}:/work/src
|
||||||
|
|
||||||
|
# Setting TZ for certificates
|
||||||
|
export TZ=UTC
|
||||||
|
# Unit Tests and some sysmte tests rely on expired certificates, turn back the time
|
||||||
|
export TEST_FAKETIME := 2025-01-02 03:04:05
|
||||||
|
|
||||||
|
# run with 'COVERAGE_SKIP=1' to skip coverage checks during system tests
|
||||||
|
ifeq ($(COVERAGE_SKIP),1)
|
||||||
|
COVERAGE_ARG_BUILD :=
|
||||||
|
COVERAGE_ARG_TEST := --coverage-skip
|
||||||
|
else
|
||||||
|
COVERAGE_ARG_BUILD := -coverpkg="./..."
|
||||||
|
COVERAGE_ARG_TEST := --coverage-dir $(COVERAGE_DIR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# export CAPUTRE=1 for regenrating test gold files
|
||||||
|
ifeq ($(CAPTURE),1)
|
||||||
|
CAPTURE_ARG := --capture
|
||||||
|
endif
|
||||||
|
|
||||||
help: ## Print this help
|
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}'
|
@grep -E '^[a-zA-Z][a-zA-Z0-9_-]*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
@@ -50,7 +69,7 @@ swagger-install:
|
|||||||
echo "// @version $(VERSION)" >> docs/swagger.conf
|
echo "// @version $(VERSION)" >> docs/swagger.conf
|
||||||
|
|
||||||
azurite-start:
|
azurite-start:
|
||||||
azurite -l /tmp/aptly-azurite & \
|
azurite -l /tmp/aptly-azurite > ~/.azurite.log 2>&1 & \
|
||||||
echo $$! > ~/.azurite.pid
|
echo $$! > ~/.azurite.pid
|
||||||
|
|
||||||
azurite-stop:
|
azurite-stop:
|
||||||
@@ -58,7 +77,7 @@ azurite-stop:
|
|||||||
|
|
||||||
swagger: swagger-install
|
swagger: swagger-install
|
||||||
# Generate swagger docs
|
# Generate swagger docs
|
||||||
@PATH=$(BINPATH)/:$(PATH) swag init --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
|
@PATH=$(BINPATH)/:$(PATH) swag init --propertyStrategy pascalcase --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
|
||||||
|
|
||||||
etcd-install:
|
etcd-install:
|
||||||
# Install etcd
|
# Install etcd
|
||||||
@@ -69,9 +88,9 @@ flake8: ## run flake8 on system test python files
|
|||||||
|
|
||||||
lint: prepare
|
lint: prepare
|
||||||
# Install golangci-lint
|
# Install golangci-lint
|
||||||
@test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
|
@test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
|
||||||
# Running lint
|
# Running lint
|
||||||
@PATH=$(BINPATH)/:$(PATH) golangci-lint run
|
@NO_COLOR=true PATH=$(BINPATH)/:$(PATH) golangci-lint run --max-issues-per-linter=0 --max-same-issues=0
|
||||||
|
|
||||||
|
|
||||||
build: prepare swagger ## Build aptly
|
build: prepare swagger ## Build aptly
|
||||||
@@ -84,11 +103,11 @@ install:
|
|||||||
# go install -v
|
# 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
|
@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
|
||||||
|
|
||||||
test: prepare swagger etcd-install ## Run unit tests
|
test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify which tests to run)
|
||||||
@echo "\e[33m\e[1mStarting etcd ...\e[0m"
|
@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 &
|
@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"
|
@echo "\e[33m\e[1mRunning go test ...\e[0m"
|
||||||
go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
|
faketime "$(TEST_FAKETIME)" go test -v ./... -gocheck.v=true -check.f "$(TEST)" -coverprofile=unit.out; echo $$? > .unit-test.ret
|
||||||
@echo "\e[33m\e[1mStopping etcd ...\e[0m"
|
@echo "\e[33m\e[1mStopping etcd ...\e[0m"
|
||||||
@pid=`cat /tmp/etcd.pid`; kill $$pid
|
@pid=`cat /tmp/etcd.pid`; kill $$pid
|
||||||
@rm -f /tmp/aptly-etcd-data/etcd.log
|
@rm -f /tmp/aptly-etcd-data/etcd.log
|
||||||
@@ -96,13 +115,13 @@ test: prepare swagger etcd-install ## Run unit tests
|
|||||||
|
|
||||||
system-test: prepare swagger etcd-install ## Run system tests
|
system-test: prepare swagger etcd-install ## Run system tests
|
||||||
# build coverage binary
|
# build coverage binary
|
||||||
go test -v -coverpkg="./..." -c -tags testruncli
|
go test -v $(COVERAGE_ARG_BUILD) -c -tags testruncli
|
||||||
# Download fixture-db, fixture-pool, etcd.db
|
# 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-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
|
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
|
||||||
test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz)
|
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
|
# Run system tests
|
||||||
PATH=$(BINPATH)/:$(PATH) && FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE) $(TEST)
|
PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long $(COVERAGE_ARG_TEST) $(CAPTURE_ARG) $(TEST)
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
|
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
|
||||||
@@ -112,7 +131,8 @@ serve: prepare swagger-install ## Run development server (auto recompiling)
|
|||||||
test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3
|
test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3
|
||||||
cp debian/aptly.conf ~/.aptly.conf
|
cp debian/aptly.conf ~/.aptly.conf
|
||||||
sed -i /enable_swagger_endpoint/s/false/true/ ~/.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
|
sed -i /enable_metrics_endpoint/s/false/true/ ~/.aptly.conf
|
||||||
|
PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --propertyStrategy pascalcase --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
|
dpkg: prepare swagger ## Build debian packages
|
||||||
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
|
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
|
||||||
@@ -162,44 +182,49 @@ binaries: prepare swagger ## Build binary releases (FreeBSD, macOS, Linux gener
|
|||||||
docker-image: ## Build aptly-dev docker image
|
docker-image: ## Build aptly-dev docker image
|
||||||
@docker build -f system/Dockerfile . -t aptly-dev
|
@docker build -f system/Dockerfile . -t aptly-dev
|
||||||
|
|
||||||
|
docker-image-no-cache: ## Build aptly-dev docker image (no cache)
|
||||||
|
@docker build --no-cache -f system/Dockerfile . -t aptly-dev
|
||||||
|
|
||||||
docker-build: ## Build aptly in docker container
|
docker-build: ## Build aptly in docker container
|
||||||
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper build
|
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper build
|
||||||
|
|
||||||
docker-shell: ## Run aptly and other commands in docker container
|
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_RUN) -it -p 3142:3142 aptly-dev /work/src/system/docker-wrapper || true
|
||||||
|
|
||||||
docker-deb: ## Build debian packages in docker container
|
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_RUN) -t aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64
|
||||||
|
|
||||||
docker-unit-test: ## Run unit tests in docker container
|
docker-unit-tests: ## Run unit tests in docker container (add TEST=regex to specify which tests to run)
|
||||||
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \
|
$(DOCKER_RUN) -t --tmpfs /smallfs:rw,size=1m aptly-dev /work/src/system/docker-wrapper \
|
||||||
azurite-start \
|
azurite-start \
|
||||||
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
|
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
|
||||||
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
|
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
|
||||||
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
|
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
|
||||||
test \
|
test TEST=$(TEST) \
|
||||||
azurite-stop
|
azurite-stop
|
||||||
|
|
||||||
docker-system-test: ## Run system tests in docker container (add TEST=t04_mirror or TEST=UpdateMirror26Test to run only specific tests)
|
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 \
|
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper \
|
||||||
azurite-start \
|
azurite-start \
|
||||||
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
|
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
|
||||||
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
|
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
|
||||||
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
|
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
|
||||||
system-test TEST=$(TEST) \
|
AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \
|
||||||
|
AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \
|
||||||
|
system-test TEST=$(TEST) CAPTURE=$(CAPTURE) COVERAGE_SKIP=$(COVERAGE_SKIP) \
|
||||||
azurite-stop
|
azurite-stop
|
||||||
|
|
||||||
docker-serve: ## Run development server (auto recompiling) on http://localhost:3142
|
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_RUN) -it -p 3142:3142 -v /tmp/cache-go-aptly:/var/lib/aptly/.cache/go-build aptly-dev /work/src/system/docker-wrapper serve || true
|
||||||
|
|
||||||
docker-lint: ## Run golangci-lint in docker container
|
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_RUN) -t aptly-dev /work/src/system/docker-wrapper lint
|
||||||
|
|
||||||
docker-binaries: ## Build binary releases (FreeBSD, macOS, Linux generic) in docker container
|
docker-binaries: ## Build binary releases (FreeBSD, macOS, Linux generic) in docker container
|
||||||
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper binaries
|
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper binaries
|
||||||
|
|
||||||
docker-man: ## Create man page in docker container
|
docker-man: ## Create man page in docker container
|
||||||
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper man
|
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper man
|
||||||
|
|
||||||
mem.png: mem.dat mem.gp
|
mem.png: mem.dat mem.gp
|
||||||
gnuplot mem.gp
|
gnuplot mem.gp
|
||||||
|
|||||||
+4
-6
@@ -63,7 +63,7 @@ Define Release APT sources in ``/etc/apt/sources.list.d/aptly.list``::
|
|||||||
|
|
||||||
deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/release DIST main
|
deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/release DIST main
|
||||||
|
|
||||||
Where DIST is one of: ``buster``, ``bullseye``, ``bookworm``, ``focal``, ``jammy``, ``noble``
|
Where DIST is one of: ``bullseye``, ``bookworm``, ``trixie``, ``focal``, ``jammy``, ``noble``
|
||||||
|
|
||||||
Install aptly packages::
|
Install aptly packages::
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ 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
|
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``
|
Where DIST is one of: ``bullseye``, ``bookworm``, ``trixie``, ``focal``, ``jammy``, ``noble``
|
||||||
|
|
||||||
Note: same gpg key is used as for the Upstream Debian Packages.
|
Note: same gpg key is used as for the Upstream Debian Packages.
|
||||||
|
|
||||||
@@ -111,10 +111,8 @@ With configuration management systems:
|
|||||||
|
|
||||||
- `Chef cookbook <https://github.com/hw-cookbooks/aptly>`_ by Aaron Baer
|
- `Chef cookbook <https://github.com/hw-cookbooks/aptly>`_ by Aaron Baer
|
||||||
(Heavy Water Operations, LLC)
|
(Heavy Water Operations, LLC)
|
||||||
- `Puppet module <https://github.com/alphagov/puppet-aptly>`_ by
|
- `Puppet module <https://github.com/voxpupuli/puppet-aptly>`_ by
|
||||||
Government Digital Services
|
Vox Pupuli
|
||||||
- `Puppet module <https://github.com/tubemogul/puppet-aptly>`_ by
|
|
||||||
TubeMogul
|
|
||||||
- `SaltStack Formula <https://github.com/saltstack-formulas/aptly-formula>`_ by
|
- `SaltStack Formula <https://github.com/saltstack-formulas/aptly-formula>`_ by
|
||||||
Forrest Alvarez and Brian Jackson
|
Forrest Alvarez and Brian Jackson
|
||||||
- `Ansible role <https://github.com/aioue/ansible-role-aptly>`_ by Tom Paine
|
- `Ansible role <https://github.com/aioue/ansible-role-aptly>`_ by Tom Paine
|
||||||
|
|||||||
+3
-1
@@ -12,5 +12,7 @@ git push origin v$version master
|
|||||||
```
|
```
|
||||||
- run swagger locally (`make docker-serve`)
|
- run swagger locally (`make docker-serve`)
|
||||||
- copy generated docs/swagger.json to https://github.com/aptly-dev/www.aptly.info/tree/master/static/swagger/aptly_1.x.y.json
|
- copy generated docs/swagger.json to https://github.com/aptly-dev/www.aptly.info/tree/master/static/swagger/aptly_1.x.y.json
|
||||||
- releae www.aptly.info
|
- add new version to select tag in content/doc/api/swagger.md line 48
|
||||||
|
- update version in content/download.md
|
||||||
|
- push commit to master
|
||||||
- create release announcement on https://github.com/aptly-dev/aptly/discussions
|
- create release announcement on https://github.com/aptly-dev/aptly/discussions
|
||||||
|
|||||||
+21
-17
@@ -41,7 +41,10 @@ type aptlyVersion struct {
|
|||||||
// @Success 200 {object} aptlyVersion
|
// @Success 200 {object} aptlyVersion
|
||||||
// @Router /api/version [get]
|
// @Router /api/version [get]
|
||||||
func apiVersion(c *gin.Context) {
|
func apiVersion(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"Version": aptly.Version})
|
version := aptlyVersion{
|
||||||
|
Version: aptly.Version,
|
||||||
|
}
|
||||||
|
c.JSON(200, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
type aptlyStatus struct {
|
type aptlyStatus struct {
|
||||||
@@ -67,7 +70,8 @@ func apiReady(isReady *atomic.Value) func(*gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{"Status": "Aptly is ready"})
|
status := aptlyStatus{Status: "Aptly is ready"}
|
||||||
|
c.JSON(200, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +169,7 @@ func runTaskInBackground(name string, resources []string, proc task.Process) (ta
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer releaseDatabaseConnection()
|
defer func() { _ = releaseDatabaseConnection() }()
|
||||||
return proc(out, detail)
|
return proc(out, detail)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -174,18 +178,18 @@ func truthy(value interface{}) bool {
|
|||||||
if value == nil {
|
if value == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
switch strings.ToLower(value.(string)) {
|
switch strings.ToLower(v) {
|
||||||
case "n", "no", "f", "false", "0", "off":
|
case "n", "no", "f", "false", "0", "off":
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case int:
|
case int:
|
||||||
return !(value.(int) == 0)
|
return v != 0
|
||||||
case bool:
|
case bool:
|
||||||
return value.(bool)
|
return v
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -210,11 +214,11 @@ func maybeRunTaskInBackground(c *gin.Context, name string, resources []string, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wait for task to finish
|
// wait for task to finish
|
||||||
context.TaskList().WaitForTaskByID(task.ID)
|
_, _ = context.TaskList().WaitForTaskByID(task.ID)
|
||||||
|
|
||||||
retValue, _ := context.TaskList().GetTaskReturnValueByID(task.ID)
|
retValue, _ := context.TaskList().GetTaskReturnValueByID(task.ID)
|
||||||
err, _ := context.TaskList().GetTaskErrorByID(task.ID)
|
err, _ := context.TaskList().GetTaskErrorByID(task.ID)
|
||||||
context.TaskList().DeleteTaskByID(task.ID)
|
_, _ = context.TaskList().DeleteTaskByID(task.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, retValue.Code, err)
|
AbortWithJSONError(c, retValue.Code, err)
|
||||||
return
|
return
|
||||||
@@ -282,11 +286,11 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
// filter packages by version
|
// filter packages by version
|
||||||
if c.Request.URL.Query().Get("maximumVersion") == "1" {
|
if c.Request.URL.Query().Get("maximumVersion") == "1" {
|
||||||
list.PrepareIndex()
|
list.PrepareIndex()
|
||||||
list.ForEach(func(p *deb.Package) error {
|
_ = list.ForEach(func(p *deb.Package) error {
|
||||||
versionQ, err := query.Parse(fmt.Sprintf("Name (%s), $Version (<= %s)", p.Name, p.Version))
|
versionQ, err := query.Parse(fmt.Sprintf("Name (%s), $Version (<= %s)", p.Name, p.Version))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("filter packages by version, query string parse err: ", err)
|
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))
|
_ = c.AbortWithError(500, fmt.Errorf("unable to parse %s maximum version query string: %s", p.Name, err))
|
||||||
} else {
|
} else {
|
||||||
tmpList, err := list.Filter(deb.FilterOptions{
|
tmpList, err := list.Filter(deb.FilterOptions{
|
||||||
Queries: []deb.PackageQuery{versionQ},
|
Queries: []deb.PackageQuery{versionQ},
|
||||||
@@ -294,15 +298,15 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if tmpList.Len() > 0 {
|
if tmpList.Len() > 0 {
|
||||||
tmpList.ForEach(func(tp *deb.Package) error {
|
_ = tmpList.ForEach(func(tp *deb.Package) error {
|
||||||
list.Remove(tp)
|
list.Remove(tp)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
list.Add(p)
|
_ = list.Add(p)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("filter packages by version, filter err: ", err)
|
fmt.Println("filter packages by version, filter err: ", err)
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to get %s maximum version: %s", p.Name, err))
|
_ = c.AbortWithError(500, fmt.Errorf("unable to get %s maximum version: %s", p.Name, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +315,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Request.URL.Query().Get("format") == "details" {
|
if c.Request.URL.Query().Get("format") == "details" {
|
||||||
list.ForEach(func(p *deb.Package) error {
|
_ = list.ForEach(func(p *deb.Package) error {
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -322,7 +326,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AbortWithJSONError(c *gin.Context, code int, err error) *gin.Error {
|
func AbortWithJSONError(c *gin.Context, code int, err error) {
|
||||||
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
return c.AbortWithError(code, err)
|
_ = c.AbortWithError(code, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -24,14 +24,14 @@ func Test(t *testing.T) {
|
|||||||
TestingT(t)
|
TestingT(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiSuite struct {
|
type APISuite struct {
|
||||||
context *ctx.AptlyContext
|
context *ctx.AptlyContext
|
||||||
flags *flag.FlagSet
|
flags *flag.FlagSet
|
||||||
configFile *os.File
|
configFile *os.File
|
||||||
router http.Handler
|
router http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Suite(&ApiSuite{})
|
var _ = Suite(&APISuite{})
|
||||||
|
|
||||||
func createTestConfig() *os.File {
|
func createTestConfig() *os.File {
|
||||||
file, err := os.CreateTemp("", "aptly")
|
file, err := os.CreateTemp("", "aptly")
|
||||||
@@ -45,11 +45,11 @@ func createTestConfig() *os.File {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
file.Write(jsonString)
|
_, _ = file.Write(jsonString)
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) setupContext() error {
|
func (s *APISuite) setupContext() error {
|
||||||
aptly.Version = "testVersion"
|
aptly.Version = "testVersion"
|
||||||
file := createTestConfig()
|
file := createTestConfig()
|
||||||
if nil == file {
|
if nil == file {
|
||||||
@@ -75,23 +75,23 @@ func (s *ApiSuite) setupContext() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) SetUpSuite(c *C) {
|
func (s *APISuite) SetUpSuite(c *C) {
|
||||||
err := s.setupContext()
|
err := s.setupContext()
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TearDownSuite(c *C) {
|
func (s *APISuite) TearDownSuite(c *C) {
|
||||||
os.Remove(s.configFile.Name())
|
_ = os.Remove(s.configFile.Name())
|
||||||
s.context.Shutdown()
|
s.context.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) SetUpTest(c *C) {
|
func (s *APISuite) SetUpTest(c *C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TearDownTest(c *C) {
|
func (s *APISuite) TearDownTest(c *C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) HTTPRequest(method string, url string, body io.Reader) (*httptest.ResponseRecorder, error) {
|
func (s *APISuite) HTTPRequest(method string, url string, body io.Reader) (*httptest.ResponseRecorder, error) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, err := http.NewRequest(method, url, body)
|
req, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,32 +102,32 @@ func (s *ApiSuite) HTTPRequest(method string, url string, body io.Reader) (*http
|
|||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestGinRunsInReleaseMode(c *C) {
|
func (s *APISuite) TestGinRunsInReleaseMode(c *C) {
|
||||||
c.Check(gin.Mode(), Equals, gin.ReleaseMode)
|
c.Check(gin.Mode(), Equals, gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestGetVersion(c *C) {
|
func (s *APISuite) TestGetVersion(c *C) {
|
||||||
response, err := s.HTTPRequest("GET", "/api/version", nil)
|
response, err := s.HTTPRequest("GET", "/api/version", nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
c.Check(response.Body.String(), Matches, "{\"Version\":\""+aptly.Version+"\"}")
|
c.Check(response.Body.String(), Matches, "{\"Version\":\""+aptly.Version+"\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestGetReadiness(c *C) {
|
func (s *APISuite) TestGetReadiness(c *C) {
|
||||||
response, err := s.HTTPRequest("GET", "/api/ready", nil)
|
response, err := s.HTTPRequest("GET", "/api/ready", nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is ready\"}")
|
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is ready\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestGetHealthiness(c *C) {
|
func (s *APISuite) TestGetHealthiness(c *C) {
|
||||||
response, err := s.HTTPRequest("GET", "/api/healthy", nil)
|
response, err := s.HTTPRequest("GET", "/api/healthy", nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is healthy\"}")
|
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is healthy\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestGetMetrics(c *C) {
|
func (s *APISuite) TestGetMetrics(c *C) {
|
||||||
response, err := s.HTTPRequest("GET", "/api/metrics", nil)
|
response, err := s.HTTPRequest("GET", "/api/metrics", nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
@@ -141,7 +141,7 @@ func (s *ApiSuite) TestGetMetrics(c *C) {
|
|||||||
c.Check(b, Matches, ".*aptly_build_info.*version=\"testVersion\".*")
|
c.Check(b, Matches, ".*aptly_build_info.*version=\"testVersion\".*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestRepoCreate(c *C) {
|
func (s *APISuite) TestRepoCreate(c *C) {
|
||||||
body, err := json.Marshal(gin.H{
|
body, err := json.Marshal(gin.H{
|
||||||
"Name": "dummy",
|
"Name": "dummy",
|
||||||
})
|
})
|
||||||
@@ -150,7 +150,7 @@ func (s *ApiSuite) TestRepoCreate(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestTruthy(c *C) {
|
func (s *APISuite) TestTruthy(c *C) {
|
||||||
c.Check(truthy("no"), Equals, false)
|
c.Check(truthy("no"), Equals, false)
|
||||||
c.Check(truthy("n"), Equals, false)
|
c.Check(truthy("n"), Equals, false)
|
||||||
c.Check(truthy("off"), Equals, false)
|
c.Check(truthy("off"), Equals, false)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
// @Success 200 {object} string "Output"
|
// @Success 200 {object} string "Output"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
// @Router /api/db/cleanup [post]
|
// @Router /api/db/cleanup [post]
|
||||||
func apiDbCleanup(c *gin.Context) {
|
func apiDBCleanup(c *gin.Context) {
|
||||||
resources := []string{string(task.AllResourcesKey)}
|
resources := []string{string(task.AllResourcesKey)}
|
||||||
maybeRunTaskInBackground(c, "Clean up db", resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, "Clean up db", resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
var err error
|
var err error
|
||||||
@@ -109,8 +109,8 @@ func apiDbCleanup(c *gin.Context) {
|
|||||||
|
|
||||||
if toDelete.Len() > 0 {
|
if toDelete.Len() > 0 {
|
||||||
batch := db.CreateBatch()
|
batch := db.CreateBatch()
|
||||||
toDelete.ForEach(func(ref []byte) error {
|
_ = toDelete.ForEach(func(ref []byte) error {
|
||||||
collectionFactory.PackageCollection().DeleteByKey(ref, batch)
|
_ = collectionFactory.PackageCollection().DeleteByKey(ref, batch)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+41
-2
@@ -13,6 +13,10 @@ import (
|
|||||||
"github.com/saracen/walker"
|
"github.com/saracen/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// syncFile is a seam to allow tests to force fsync failures (e.g. ENOSPC).
|
||||||
|
// In production it calls (*os.File).Sync().
|
||||||
|
var syncFile = func(f *os.File) error { return f.Sync() }
|
||||||
|
|
||||||
func verifyPath(path string) bool {
|
func verifyPath(path string) bool {
|
||||||
path = filepath.Clean(path)
|
path = filepath.Clean(path)
|
||||||
for _, part := range strings.Split(path, string(filepath.Separator)) {
|
for _, part := range strings.Split(path, string(filepath.Separator)) {
|
||||||
@@ -114,34 +118,69 @@ func apiFilesUpload(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stored := []string{}
|
stored := []string{}
|
||||||
|
openFiles := []*os.File{}
|
||||||
|
|
||||||
|
// Write all files first
|
||||||
for _, files := range c.Request.MultipartForm.File {
|
for _, files := range c.Request.MultipartForm.File {
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
src, err := file.Open()
|
src, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Close any files we've opened
|
||||||
|
for _, f := range openFiles {
|
||||||
|
_ = f.Close()
|
||||||
|
}
|
||||||
AbortWithJSONError(c, 500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
destPath := filepath.Join(path, filepath.Base(file.Filename))
|
destPath := filepath.Join(path, filepath.Base(file.Filename))
|
||||||
dst, err := os.Create(destPath)
|
dst, err := os.Create(destPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = src.Close()
|
||||||
|
// Close any files we've opened
|
||||||
|
for _, f := range openFiles {
|
||||||
|
_ = f.Close()
|
||||||
|
}
|
||||||
AbortWithJSONError(c, 500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer dst.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(dst, src)
|
_, err = io.Copy(dst, src)
|
||||||
|
_ = src.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = dst.Close()
|
||||||
|
// Close any files we've opened
|
||||||
|
for _, f := range openFiles {
|
||||||
|
_ = f.Close()
|
||||||
|
}
|
||||||
AbortWithJSONError(c, 500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep file open for batch sync
|
||||||
|
openFiles = append(openFiles, dst)
|
||||||
stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename)))
|
stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync all files at once to catch ENOSPC errors
|
||||||
|
for i, dst := range openFiles {
|
||||||
|
err := syncFile(dst)
|
||||||
|
if err != nil {
|
||||||
|
// Close all files
|
||||||
|
for _, f := range openFiles {
|
||||||
|
_ = f.Close()
|
||||||
|
}
|
||||||
|
AbortWithJSONError(c, 500, fmt.Errorf("error syncing file %s: %s", stored[i], err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all files
|
||||||
|
for _, dst := range openFiles {
|
||||||
|
_ = dst.Close()
|
||||||
|
}
|
||||||
|
|
||||||
apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc()
|
apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc()
|
||||||
c.JSON(200, stored)
|
c.JSON(200, stored)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,476 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilesUploadDiskFullSuite struct {
|
||||||
|
aptlyContext *ctx.AptlyContext
|
||||||
|
flags *flag.FlagSet
|
||||||
|
configFile *os.File
|
||||||
|
router http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&FilesUploadDiskFullSuite{})
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) SetUpTest(c *C) {
|
||||||
|
aptly.Version = "testVersion"
|
||||||
|
|
||||||
|
file, err := os.CreateTemp("", "aptly")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
s.configFile = file
|
||||||
|
|
||||||
|
jsonString, err := json.Marshal(gin.H{
|
||||||
|
"architectures": []string{},
|
||||||
|
"rootDir": c.MkDir(),
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = file.Write(jsonString)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_ = file.Close()
|
||||||
|
|
||||||
|
flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
|
||||||
|
flags.Bool("no-lock", false, "dummy")
|
||||||
|
flags.Int("db-open-attempts", 3, "dummy")
|
||||||
|
flags.String("config", s.configFile.Name(), "dummy")
|
||||||
|
flags.String("architectures", "", "dummy")
|
||||||
|
s.flags = flags
|
||||||
|
|
||||||
|
aptlyContext, err := ctx.NewContext(s.flags)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
s.aptlyContext = aptlyContext
|
||||||
|
s.router = Router(aptlyContext)
|
||||||
|
context = aptlyContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TearDownTest(c *C) {
|
||||||
|
if s.configFile != nil {
|
||||||
|
_ = os.Remove(s.configFile.Name())
|
||||||
|
}
|
||||||
|
if s.aptlyContext != nil {
|
||||||
|
s.aptlyContext.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadSuccessWithSync(c *C) {
|
||||||
|
testContent := []byte("test file content for upload")
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
part, err := writer.CreateFormFile("file", "testfile.txt")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
_, err = part.Write(testContent)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/testdir", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
|
||||||
|
uploadedFile := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "testdir", "testfile.txt")
|
||||||
|
content, err := os.ReadFile(uploadedFile)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(content, DeepEquals, testContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadVerifiesFileIntegrity(c *C) {
|
||||||
|
testContent := bytes.Repeat([]byte("A"), 10000)
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
part, err := writer.CreateFormFile("file", "largefile.bin")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
_, err = io.Copy(part, bytes.NewReader(testContent))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/testdir2", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
|
||||||
|
uploadedFile := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "testdir2", "largefile.bin")
|
||||||
|
content, err := os.ReadFile(uploadedFile)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(len(content), Equals, len(testContent))
|
||||||
|
c.Check(content, DeepEquals, testContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadMultipleFilesWithBatchSync(c *C) {
|
||||||
|
testFiles := map[string][]byte{
|
||||||
|
"file1.txt": []byte("content of file 1"),
|
||||||
|
"file2.txt": bytes.Repeat([]byte("B"), 5000),
|
||||||
|
"file3.deb": []byte("debian package content"),
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
for filename, content := range testFiles {
|
||||||
|
part, err := writer.CreateFormFile("file", filename)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = part.Write(content)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writer.Close()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/multitest", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
|
||||||
|
uploadDir := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "multitest")
|
||||||
|
for filename, expectedContent := range testFiles {
|
||||||
|
uploadedFile := filepath.Join(uploadDir, filename)
|
||||||
|
content, err := os.ReadFile(uploadedFile)
|
||||||
|
c.Assert(err, IsNil, Commentf("Failed to read %s", filename))
|
||||||
|
c.Check(content, DeepEquals, expectedContent, Commentf("Content mismatch for %s", filename))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadReturnsErrorOnSyncFailure(c *C) {
|
||||||
|
oldSyncFile := syncFile
|
||||||
|
syncFile = func(f *os.File) error {
|
||||||
|
if filepath.Base(f.Name()) == "syncfail.txt" {
|
||||||
|
return syscall.ENOSPC
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer func() { syncFile = oldSyncFile }()
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
part1, err := writer.CreateFormFile("file", "ok.txt")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = part1.Write([]byte("ok"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
part2, err := writer.CreateFormFile("file", "syncfail.txt")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = part2.Write([]byte("will fail on sync"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/syncfaildir", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
c.Check(bytes.Contains(w.Body.Bytes(), []byte("error syncing file")), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestVerifyPath(c *C) {
|
||||||
|
c.Check(verifyPath("a/b/c"), Equals, true)
|
||||||
|
c.Check(verifyPath("../x"), Equals, false)
|
||||||
|
c.Check(verifyPath("./x"), Equals, true)
|
||||||
|
c.Check(verifyPath(".."), Equals, false)
|
||||||
|
c.Check(verifyPath("."), Equals, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestListDirsEmptyWhenUploadMissing(c *C) {
|
||||||
|
_ = os.RemoveAll(s.aptlyContext.UploadPath())
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/api/files", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
c.Check(strings.TrimSpace(w.Body.String()), Equals, "[]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestListDirsReturnsDirectories(c *C) {
|
||||||
|
uploadRoot := s.aptlyContext.UploadPath()
|
||||||
|
c.Assert(os.MkdirAll(filepath.Join(uploadRoot, "d1"), 0777), IsNil)
|
||||||
|
c.Assert(os.MkdirAll(filepath.Join(uploadRoot, "d2"), 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(uploadRoot, "rootfile"), []byte("x"), 0644), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/api/files", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
body := w.Body.String()
|
||||||
|
c.Check(strings.Contains(body, "d1"), Equals, true)
|
||||||
|
c.Check(strings.Contains(body, "d2"), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestListFilesNotFound(c *C) {
|
||||||
|
req, err := http.NewRequest("GET", "/api/files/does-not-exist", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestListFilesReturnsFiles(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "dir")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(base, "b.txt"), []byte("b"), 0644), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/api/files/dir", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
body := w.Body.String()
|
||||||
|
c.Check(strings.Contains(body, "a.txt"), Equals, true)
|
||||||
|
c.Check(strings.Contains(body, "b.txt"), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestDeleteDirRemovesDirectory(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "todel")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/todel", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
|
||||||
|
_, statErr := os.Stat(base)
|
||||||
|
c.Check(os.IsNotExist(statErr), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestDeleteFileRemovesFile(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "todel2")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/todel2/a.txt", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
|
||||||
|
_, statErr := os.Stat(filepath.Join(base, "a.txt"))
|
||||||
|
c.Check(os.IsNotExist(statErr), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestDeleteFileNotFoundStillOk(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "todel3")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/todel3/nope.txt", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestRejectsInvalidDir(c *C) {
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/..", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestRejectsInvalidFileName(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "dirx")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/dirx/..", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestListDirsEmptyIfUploadPathIsNotDir(c *C) {
|
||||||
|
_ = os.RemoveAll(s.aptlyContext.UploadPath())
|
||||||
|
c.Assert(os.WriteFile(s.aptlyContext.UploadPath(), []byte("not a dir"), 0644), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/api/files", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 200)
|
||||||
|
c.Check(strings.TrimSpace(w.Body.String()), Equals, "[]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestListFilesReturns500OnPermissionError(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "noperms")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
||||||
|
c.Assert(os.Chmod(base, 0), IsNil)
|
||||||
|
defer func() { _ = os.Chmod(base, 0777) }()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/api/files/noperms", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestDeleteFileReturns500OnNonNotExistError(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "dirisfile")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
subdir := filepath.Join(base, "subdir")
|
||||||
|
c.Assert(os.MkdirAll(subdir, 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(subdir, "x"), []byte("x"), 0644), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/dirisfile/subdir", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadBadMultipartReturns400(c *C) {
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/badmultipart", bytes.NewBufferString("not multipart"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", "multipart/form-data; boundary=missing")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
c.Assert(w.Code, Equals, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadRejectsInvalidDir(c *C) {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("file", "a.txt")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = part.Write([]byte("x"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(writer.Close(), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/..", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadReturns500IfUploadRootIsNotDir(c *C) {
|
||||||
|
_ = os.RemoveAll(s.aptlyContext.UploadPath())
|
||||||
|
c.Assert(os.WriteFile(s.aptlyContext.UploadPath(), []byte("not a dir"), 0644), IsNil)
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("file", "a.txt")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = part.Write([]byte("x"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(writer.Close(), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/testdir", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadReturns500OnFileOpenFailure(c *C) {
|
||||||
|
// Pre-populate MultipartForm to inject a FileHeader that fails on Open().
|
||||||
|
form := &multipart.Form{
|
||||||
|
File: map[string][]*multipart.FileHeader{
|
||||||
|
"file": {{Filename: "broken.bin"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/openfaildir", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.MultipartForm = form
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestUploadReturns500OnCreateFailure(c *C) {
|
||||||
|
base := filepath.Join(s.aptlyContext.UploadPath(), "readonly")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
c.Assert(os.Chmod(base, 0555), IsNil)
|
||||||
|
defer func() { _ = os.Chmod(base, 0777) }()
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("file", "a.txt")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = part.Write([]byte("x"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(writer.Close(), IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/api/files/readonly", body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FilesUploadDiskFullSuite) TestDeleteDirReturns500OnRemoveFailure(c *C) {
|
||||||
|
parent := s.aptlyContext.UploadPath()
|
||||||
|
base := filepath.Join(parent, "cantremove")
|
||||||
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
||||||
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
||||||
|
|
||||||
|
c.Assert(os.Chmod(parent, 0555), IsNil)
|
||||||
|
defer func() { _ = os.Chmod(parent, 0777) }()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", "/api/files/cantremove", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
c.Assert(w.Code, Equals, 500)
|
||||||
|
}
|
||||||
+219
-10
@@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -12,27 +13,60 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type gpgKeyInfo struct {
|
||||||
|
// 16-character key ID (short form)
|
||||||
|
KeyID string `json:"KeyID" example:"8B48AD6246925553"`
|
||||||
|
// Full fingerprint
|
||||||
|
Fingerprint string `json:"Fingerprint" example:"D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0"`
|
||||||
|
// Key validity (u=unknown, f=fulltrust, m=marginal, n=never)
|
||||||
|
Validity string `json:"Validity" example:"u"`
|
||||||
|
// User ID(s) associated with this key
|
||||||
|
UserIDs []string `json:"UserIDs" example:"John Doe <john@example.com>"`
|
||||||
|
// Creation date (Unix timestamp format from gpg)
|
||||||
|
CreatedAt string `json:"CreatedAt" example:"2023-01-15"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type gpgKeyListResponse struct {
|
||||||
|
Keys []gpgKeyInfo `json:"Keys"`
|
||||||
|
}
|
||||||
|
|
||||||
type gpgAddKeyParams struct {
|
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 for adding the keys (default: trustedkeys.gpg)
|
||||||
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
|
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
|
||||||
|
|
||||||
|
// Add ASCII armored gpg public key, do not download from keyserver
|
||||||
|
GpgKeyArmor string `json:"GpgKeyArmor" example:""`
|
||||||
|
|
||||||
|
// Keyserver to download keys provided in `GpgKeyID`
|
||||||
|
Keyserver string `json:"Keyserver" example:"hkp://keyserver.ubuntu.com:80"`
|
||||||
|
// Keys do download from `Keyserver`, separated by space
|
||||||
|
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500 8B48AD6246925553"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type gpgDeleteKeyParams struct {
|
||||||
|
// Keyring to delete keys from (default: trustedkeys.gpg)
|
||||||
|
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
|
||||||
|
|
||||||
|
// Key ID or fingerprint to delete
|
||||||
|
GpgKeyID string `json:"GpgKeyID" example:"8B48AD6246925553"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Add GPG Keys
|
// @Summary Add GPG Keys
|
||||||
// @Description **Adds GPG keys to aptly keyring**
|
// @Description **Adds GPG keys to aptly keyring**
|
||||||
// @Description
|
// @Description
|
||||||
// @Description Add GPG public keys for veryfing remote repositories for mirroring.
|
// @Description Add GPG public keys for verifying remote repositories for mirroring.
|
||||||
|
// @Description
|
||||||
|
// @Description Keys can be added in two ways:
|
||||||
|
// @Description * By providing the ASCII armord key in `GpgKeyArmor` (leave Keyserver and GpgKeyID empty)
|
||||||
|
// @Description * By providing a `Keyserver` and one or more key IDs in `GpgKeyID`, separated by space (leave GpgKeyArmor empty)
|
||||||
|
// @Description
|
||||||
// @Tags Mirrors
|
// @Tags Mirrors
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body gpgAddKeyParams true "Parameters"
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} string "OK"
|
// @Success 200 {object} string "OK"
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Router /api/gpg/key [post]
|
||||||
// @Router /api/gpg [post]
|
|
||||||
func apiGPGAddKey(c *gin.Context) {
|
func apiGPGAddKey(c *gin.Context) {
|
||||||
b := gpgAddKeyParams{}
|
b := gpgAddKeyParams{}
|
||||||
if c.Bind(&b) != nil {
|
if c.Bind(&b) != nil {
|
||||||
@@ -60,7 +94,7 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
AbortWithJSONError(c, 400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tempdir)
|
defer func() { _ = os.RemoveAll(tempdir) }()
|
||||||
|
|
||||||
keypath := filepath.Join(tempdir, "key")
|
keypath := filepath.Join(tempdir, "key")
|
||||||
keyfile, e := os.Create(keypath)
|
keyfile, e := os.Create(keypath)
|
||||||
@@ -100,3 +134,178 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(200, string(out))
|
c.JSON(200, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary List GPG Keys
|
||||||
|
// @Description **Lists all GPG keys in aptly keyring**
|
||||||
|
// @Description
|
||||||
|
// @Description Returns all public keys currently installed in the aptly GPG keyring.
|
||||||
|
// @Description
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Param keyring query string false "Keyring file to list keys from (default: trustedkeys.gpg)" example(trustedkeys.gpg)
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} gpgKeyListResponse "OK"
|
||||||
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
|
// @Router /api/gpg/keys [get]
|
||||||
|
func apiGPGListKeys(c *gin.Context) {
|
||||||
|
keyring := c.DefaultQuery("keyring", "trustedkeys.gpg")
|
||||||
|
keyring = utils.SanitizePath(keyring)
|
||||||
|
|
||||||
|
finder := pgp.GPGDefaultFinder()
|
||||||
|
gpg, _, err := finder.FindGPG()
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 400, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"--no-default-keyring",
|
||||||
|
"--with-colons",
|
||||||
|
"--keyring", keyring,
|
||||||
|
"--list-keys",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(gpg, args...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("failed to list keys: %s", string(out)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := parseGPGOutput(string(out))
|
||||||
|
c.JSON(200, gpgKeyListResponse{Keys: keys})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Delete GPG Key
|
||||||
|
// @Description **Deletes a GPG key from aptly keyring**
|
||||||
|
// @Description
|
||||||
|
// @Description Removes a public key from the aptly GPG keyring. This is useful for removing
|
||||||
|
// @Description compromised keys or cleaning up obsolete keys.
|
||||||
|
// @Description
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body gpgDeleteKeyParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} string "OK"
|
||||||
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
|
// @Router /api/gpg/key [delete]
|
||||||
|
func apiGPGDeleteKey(c *gin.Context) {
|
||||||
|
b := gpgDeleteKeyParams{}
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("invalid request body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(b.GpgKeyID)) == 0 {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("GpgKeyID is required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.GpgKeyID = utils.SanitizePath(b.GpgKeyID)
|
||||||
|
// b.Keyring can be an absolute path
|
||||||
|
|
||||||
|
finder := pgp.GPGDefaultFinder()
|
||||||
|
gpg, _, err := finder.FindGPG()
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 400, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"--no-default-keyring",
|
||||||
|
"--allow-non-selfsigned-uid",
|
||||||
|
"--batch",
|
||||||
|
"--yes",
|
||||||
|
}
|
||||||
|
|
||||||
|
keyring := "trustedkeys.gpg"
|
||||||
|
if len(b.Keyring) > 0 {
|
||||||
|
keyring = b.Keyring
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "--keyring", keyring)
|
||||||
|
args = append(args, "--delete-keys", b.GpgKeyID)
|
||||||
|
|
||||||
|
cmd := exec.Command(gpg, args...)
|
||||||
|
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("failed to delete key: %s", string(out)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGPGOutput parses the output of `gpg --with-colons --list-keys`
|
||||||
|
// and returns a structured list of keys
|
||||||
|
func parseGPGOutput(output string) []gpgKeyInfo {
|
||||||
|
var keys []gpgKeyInfo
|
||||||
|
var currentKey *gpgKeyInfo
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(output))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) < 10 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
recordType := parts[0]
|
||||||
|
|
||||||
|
// pub: public key record
|
||||||
|
if recordType == "pub" {
|
||||||
|
// Save previous key if it exists
|
||||||
|
if currentKey != nil && currentKey.KeyID != "" {
|
||||||
|
keys = append(keys, *currentKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new key entry
|
||||||
|
// Format: pub:trust:length:algo:keyid:created:expires:uidhash:...
|
||||||
|
keyID := parts[4]
|
||||||
|
if len(keyID) >= 16 {
|
||||||
|
keyID = keyID[len(keyID)-16:] // Last 16 chars = short key ID
|
||||||
|
}
|
||||||
|
validity := parts[1]
|
||||||
|
createdAt := parts[5]
|
||||||
|
|
||||||
|
currentKey = &gpgKeyInfo{
|
||||||
|
KeyID: keyID,
|
||||||
|
Validity: validity,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UserIDs: []string{},
|
||||||
|
Fingerprint: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// uid: user ID record
|
||||||
|
if recordType == "uid" && currentKey != nil {
|
||||||
|
// Format: uid:trust:created:expires:keyid:uidhash:uidtype:validity:userID:...
|
||||||
|
if len(parts) >= 10 {
|
||||||
|
userID := parts[9]
|
||||||
|
if userID != "" {
|
||||||
|
currentKey.UserIDs = append(currentKey.UserIDs, userID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fpr: fingerprint record
|
||||||
|
if recordType == "fpr" && currentKey != nil {
|
||||||
|
// Format: fpr:::::::::fingerprint:
|
||||||
|
if len(parts) >= 10 {
|
||||||
|
fingerprint := parts[9]
|
||||||
|
currentKey.Fingerprint = fingerprint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't forget the last key
|
||||||
|
if currentKey != nil && currentKey.KeyID != "" {
|
||||||
|
keys = append(keys, *currentKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|||||||
+451
@@ -0,0 +1,451 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GPGSuite struct {
|
||||||
|
APISuite
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&GPGSuite{})
|
||||||
|
|
||||||
|
func (s *GPGSuite) withFakeGPG(c *C, scriptBody string, test func(scriptPath string)) {
|
||||||
|
tempDir, err := os.MkdirTemp("", "aptly-fake-gpg")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
||||||
|
|
||||||
|
scriptPath := filepath.Join(tempDir, "gpg")
|
||||||
|
err = os.WriteFile(scriptPath, []byte(scriptBody), 0o755)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
oldPath := os.Getenv("PATH")
|
||||||
|
err = os.Setenv("PATH", tempDir+string(os.PathListSeparator)+oldPath)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer func() { _ = os.Setenv("PATH", oldPath) }()
|
||||||
|
|
||||||
|
test(scriptPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GPGSuite) fakeGPGScript(c *C, listOutput string, deleteOutput string, deleteError string) string {
|
||||||
|
return "#!/bin/sh\n" +
|
||||||
|
"if [ \"$1\" = \"--version\" ]; then\n" +
|
||||||
|
" echo 'gpg (GnuPG) 2.2.27'\n" +
|
||||||
|
" exit 0\n" +
|
||||||
|
"fi\n" +
|
||||||
|
"args=\"$*\"\n" +
|
||||||
|
"if printf '%s' \"$args\" | grep -q -- '--list-keys'; then\n" +
|
||||||
|
" cat <<'EOF'\n" + listOutput + "\nEOF\n" +
|
||||||
|
" exit 0\n" +
|
||||||
|
"fi\n" +
|
||||||
|
"if printf '%s' \"$args\" | grep -q -- '--delete-keys'; then\n" +
|
||||||
|
" if [ -n \"" + strings.ReplaceAll(deleteError, "\n", "") + "\" ]; then\n" +
|
||||||
|
" echo '" + strings.ReplaceAll(deleteError, "'", "'\\''") + "'\n" +
|
||||||
|
" exit 1\n" +
|
||||||
|
" fi\n" +
|
||||||
|
" cat <<'EOF'\n" + deleteOutput + "\nEOF\n" +
|
||||||
|
" exit 0\n" +
|
||||||
|
"fi\n" +
|
||||||
|
"echo 'unexpected invocation' >&2\n" +
|
||||||
|
"exit 1\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputEmpty tests parsing of empty GPG output
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputEmpty(c *C) {
|
||||||
|
output := ""
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputSingleKeyMinimal tests parsing a single key with minimal fields
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputSingleKeyMinimal(c *C) {
|
||||||
|
// Minimal valid GPG output with one key
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
|
||||||
|
key := keys[0]
|
||||||
|
c.Check(key.KeyID, Equals, "8B48AD6246925553")
|
||||||
|
c.Check(key.Validity, Equals, "u")
|
||||||
|
c.Check(key.CreatedAt, Equals, "1611864000")
|
||||||
|
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
||||||
|
c.Check(key.UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputMultipleKeys tests parsing multiple keys
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputMultipleKeys(c *C) {
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
|
||||||
|
pub:f:2048:1:A1B2C3D4E5F67890:1580592000:1612128000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:f::::1580592000::0987654321::Jane Smith <jane@example.com>::::::::::0:
|
||||||
|
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 2)
|
||||||
|
|
||||||
|
// First key
|
||||||
|
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
||||||
|
c.Check(keys[0].Validity, Equals, "u")
|
||||||
|
c.Check(keys[0].UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
|
||||||
|
|
||||||
|
// Second key
|
||||||
|
c.Check(keys[1].KeyID, Equals, "A1B2C3D4E5F67890")
|
||||||
|
c.Check(keys[1].Validity, Equals, "f")
|
||||||
|
c.Check(keys[1].UserIDs, DeepEquals, []string{"Jane Smith <jane@example.com>"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputMultipleUIDs tests a key with multiple user IDs
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputMultipleUIDs(c *C) {
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
uid:u::::1611864000::1234567891::John Doe <john.doe@company.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
|
||||||
|
key := keys[0]
|
||||||
|
c.Check(key.UserIDs, HasLen, 2)
|
||||||
|
c.Check(key.UserIDs, DeepEquals, []string{
|
||||||
|
"John Doe <john@example.com>",
|
||||||
|
"John Doe <john.doe@company.com>",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputMalformedLines tests that malformed lines are skipped
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputMalformedLines(c *C) {
|
||||||
|
// Mix of valid and invalid lines (too few fields)
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
invalid:line:with:only:three:fields
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputEmptyLines tests that empty lines are skipped
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputEmptyLines(c *C) {
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputKeyWithoutUID tests a public key without user ID
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputKeyWithoutUID(c *C) {
|
||||||
|
// Key without uid record (should still be included)
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
|
||||||
|
key := keys[0]
|
||||||
|
c.Check(key.KeyID, Equals, "8B48AD6246925553")
|
||||||
|
c.Check(key.UserIDs, HasLen, 0)
|
||||||
|
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputVariousValidity tests different validity values
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputVariousValidity(c *C) {
|
||||||
|
output := `pub:u:4096:1:KEY1111111111111:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::Key1::::::::::0:
|
||||||
|
fpr:::::::::1111111111111111111111111111111111111111:
|
||||||
|
pub:f:4096:1:KEY2222222222222:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:f::::1611864000::1234567891::Key2::::::::::0:
|
||||||
|
fpr:::::::::2222222222222222222222222222222222222222:
|
||||||
|
pub:m:4096:1:KEY3333333333333:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:m::::1611864000::1234567892::Key3::::::::::0:
|
||||||
|
fpr:::::::::3333333333333333333333333333333333333333:
|
||||||
|
pub:n:4096:1:KEY4444444444444:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:n::::1611864000::1234567893::Key4::::::::::0:
|
||||||
|
fpr:::::::::4444444444444444444444444444444444444444:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 4)
|
||||||
|
|
||||||
|
validities := []string{"u", "f", "m", "n"}
|
||||||
|
for i, validity := range validities {
|
||||||
|
c.Check(keys[i].Validity, Equals, validity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputShortKeyID tests that key IDs are shortened to 16 chars
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputShortKeyID(c *C) {
|
||||||
|
// 40-character key ID that should be shortened to last 16 chars
|
||||||
|
longKeyID := "0123456789ABCDEF0123456789ABCDEF8B48AD62"
|
||||||
|
output := `pub:u:4096:1:` + longKeyID + `:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
// Should extract the last 16 characters: 89ABCDEF8B48AD62
|
||||||
|
c.Check(keys[0].KeyID, Equals, "89ABCDEF8B48AD62")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputSpecialCharactersInUID tests user IDs with special characters
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputSpecialCharactersInUID(c *C) {
|
||||||
|
// UID with Unicode characters and special formatting
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::J\xc3\xb6hn D\xc3\xb6\xc3\xa9 (D\xc3\xbcss) <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
// Should preserve the encoded special characters
|
||||||
|
c.Check(keys[0].UserIDs, HasLen, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGListKeysDefaultKeyring tests the HTTP endpoint with default keyring
|
||||||
|
func (s *GPGSuite) TestAPIGPGListKeysDefaultKeyring(c *C) {
|
||||||
|
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:u:4096:1:8B48AD6246925553:1611864000:::::
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`, "", ""), func(_ string) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
|
||||||
|
var result gpgKeyListResponse
|
||||||
|
err = json.NewDecoder(response.Body).Decode(&result)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(result.Keys, HasLen, 1)
|
||||||
|
c.Check(result.Keys[0].KeyID, Equals, "8B48AD6246925553")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGListKeysWithKeyringParam tests the HTTP endpoint with custom keyring parameter
|
||||||
|
func (s *GPGSuite) TestAPIGPGListKeysWithKeyringParam(c *C) {
|
||||||
|
argFile, err := os.CreateTemp("", "aptly-gpg-args")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_ = argFile.Close()
|
||||||
|
defer func() { _ = os.Remove(argFile.Name()) }()
|
||||||
|
|
||||||
|
script := "#!/bin/sh\n" +
|
||||||
|
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
|
||||||
|
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
|
||||||
|
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
|
||||||
|
"cat <<'EOF'\n" +
|
||||||
|
"pub:u:4096:1:8B48AD6246925553:1611864000:::::\n" +
|
||||||
|
"fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:\n" +
|
||||||
|
"EOF\n" +
|
||||||
|
"exit 0\n" +
|
||||||
|
"fi\n" +
|
||||||
|
"exit 1\n"
|
||||||
|
|
||||||
|
s.withFakeGPG(c, script, func(_ string) {
|
||||||
|
response, reqErr := s.HTTPRequest("GET", "/api/gpg/keys?keyring=/custom.gpg", nil)
|
||||||
|
c.Assert(reqErr, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
|
||||||
|
argBytes, readErr := os.ReadFile(argFile.Name())
|
||||||
|
c.Assert(readErr, IsNil)
|
||||||
|
c.Check(string(argBytes), Matches, `(?s).*--keyring\ncustom\.gpg\n.*`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGListKeysResponseFormat tests that the response has the correct structure
|
||||||
|
func (s *GPGSuite) TestAPIGPGListKeysResponseFormat(c *C) {
|
||||||
|
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:f:4096:1:A1B2C3D4E5F67890:1611864000:::::
|
||||||
|
uid:f::::1611864000::1234567890::Jane Smith <jane@example.com>::::::::::0:
|
||||||
|
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`, "", ""), func(_ string) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
|
||||||
|
var result gpgKeyListResponse
|
||||||
|
err = json.NewDecoder(response.Body).Decode(&result)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(result.Keys, HasLen, 1)
|
||||||
|
c.Check(result.Keys[0].KeyID, Equals, "A1B2C3D4E5F67890")
|
||||||
|
c.Check(result.Keys[0].Validity, Equals, "f")
|
||||||
|
c.Check(result.Keys[0].CreatedAt, Equals, "1611864000")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputEdgeCaseUIDWithoutFields tests UID record with missing fields
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputEdgeCaseUIDWithoutFields(c *C) {
|
||||||
|
// UID record with fewer than 10 fields
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
// Should not have user ID since it's in field 9 and this record is too short
|
||||||
|
c.Check(keys[0].UserIDs, HasLen, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputFingerprintWithoutCurrentKey tests FPR record appearing before any PUB
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputFingerprintWithoutCurrentKey(c *C) {
|
||||||
|
// FPR record without a preceding PUB (should be ignored)
|
||||||
|
output := `fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
|
||||||
|
pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
// Should only have one key with the correct fingerprint
|
||||||
|
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputComplexRealWorldExample tests real-world-like GPG output
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputComplexRealWorldExample(c *C) {
|
||||||
|
// Real-world GPG output with multiple keys, UIDs, and other record types (sig, sub)
|
||||||
|
// Note: sub and sig records are skipped as we only care about pub/uid/fpr
|
||||||
|
realWorldOutput := `tru::1:1611864000:0:3:1:5
|
||||||
|
pub:u:4096:1:8B48AD6246925553:1611864000:2023-01-15T00:00:00:::::scESC:::::::23::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
|
||||||
|
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
||||||
|
uid:u::::1611864100::1234567891::John Doe <john@work.com>::::::::::0:
|
||||||
|
pub:f:2048:1:1234567890123456:1580592000:2022-12-31T00:00:00::u:::scESC:::::::23::0:
|
||||||
|
fpr:::::::::F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9:
|
||||||
|
uid:f::::1580592000::0987654321::Maintainer Key <maint@example.com>::::::::::0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(realWorldOutput)
|
||||||
|
c.Check(keys, HasLen, 2)
|
||||||
|
|
||||||
|
// First key should have 2 UIDs
|
||||||
|
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
||||||
|
c.Check(keys[0].UserIDs, HasLen, 2)
|
||||||
|
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
||||||
|
|
||||||
|
// Second key should have 1 UID
|
||||||
|
c.Check(keys[1].KeyID, Equals, "1234567890123456")
|
||||||
|
c.Check(keys[1].UserIDs, HasLen, 1)
|
||||||
|
c.Check(keys[1].Fingerprint, Equals, "F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseGPGOutputConsecutiveEmptyUIDs tests handling of consecutive empty user ID fields
|
||||||
|
func (s *GPGSuite) TestParseGPGOutputConsecutiveEmptyUIDs(c *C) {
|
||||||
|
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
||||||
|
uid:u::::1611864000::1234567890:::::::::::0:
|
||||||
|
uid:u::::1611864000::1234567891::John Doe <john@example.com>::::::::::0:
|
||||||
|
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
||||||
|
|
||||||
|
keys := parseGPGOutput(output)
|
||||||
|
c.Check(keys, HasLen, 1)
|
||||||
|
// Should skip empty UID but include the non-empty one
|
||||||
|
c.Check(keys[0].UserIDs, HasLen, 1)
|
||||||
|
c.Check(keys[0].UserIDs[0], Equals, "John Doe <john@example.com>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGPGDeleteKeyParamsValidation tests gpgDeleteKeyParams validation
|
||||||
|
func (s *GPGSuite) TestGPGDeleteKeyParamsValidation(c *C) {
|
||||||
|
// This is a unit test that validates parameter structure (no HTTP needed)
|
||||||
|
params := gpgDeleteKeyParams{
|
||||||
|
Keyring: "custom.gpg",
|
||||||
|
GpgKeyID: "8B48AD6246925553",
|
||||||
|
}
|
||||||
|
c.Check(params.Keyring, Equals, "custom.gpg")
|
||||||
|
c.Check(params.GpgKeyID, Equals, "8B48AD6246925553")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGDeleteKeyMissingKeyID tests delete with missing key ID parameter
|
||||||
|
func (s *GPGSuite) TestAPIGPGDeleteKeyMissingKeyID(c *C) {
|
||||||
|
body, err := json.Marshal(map[string]string{
|
||||||
|
"Keyring": "trustedkeys.gpg",
|
||||||
|
// GpgKeyID is missing
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGDeleteKeyInvalidJSON tests delete with invalid JSON request
|
||||||
|
func (s *GPGSuite) TestAPIGPGDeleteKeyInvalidJSON(c *C) {
|
||||||
|
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader([]byte("invalid json")))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGDeleteKeySuccess tests successful key deletion
|
||||||
|
func (s *GPGSuite) TestAPIGPGDeleteKeySuccess(c *C) {
|
||||||
|
argFile, err := os.CreateTemp("", "aptly-gpg-delete-args")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_ = argFile.Close()
|
||||||
|
defer func() { _ = os.Remove(argFile.Name()) }()
|
||||||
|
|
||||||
|
script := "#!/bin/sh\n" +
|
||||||
|
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
|
||||||
|
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
|
||||||
|
"if printf '%s' \"$*\" | grep -q -- '--delete-keys'; then\n" +
|
||||||
|
"echo 'deleted'\n" +
|
||||||
|
"exit 0\n" +
|
||||||
|
"fi\n" +
|
||||||
|
"exit 1\n"
|
||||||
|
|
||||||
|
s.withFakeGPG(c, script, func(_ string) {
|
||||||
|
body, marshalErr := json.Marshal(gpgDeleteKeyParams{
|
||||||
|
Keyring: "/trustedkeys.gpg",
|
||||||
|
GpgKeyID: "8B48AD6246925553",
|
||||||
|
})
|
||||||
|
c.Assert(marshalErr, IsNil)
|
||||||
|
|
||||||
|
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
|
||||||
|
c.Assert(reqErr, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
c.Check(response.Body.String(), Matches, `"deleted\\n"`)
|
||||||
|
|
||||||
|
argBytes, readErr := os.ReadFile(argFile.Name())
|
||||||
|
c.Assert(readErr, IsNil)
|
||||||
|
argText := string(argBytes)
|
||||||
|
c.Check(argText, Matches, `(?s).*--batch\n--yes\n.*`)
|
||||||
|
c.Check(argText, Matches, `(?s).*--keyring\n/trustedkeys\.gpg\n.*`)
|
||||||
|
c.Check(argText, Matches, `(?s).*--delete-keys\n8B48AD6246925553\n.*`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGListKeysCommandFailure tests list error propagation from gpg
|
||||||
|
func (s *GPGSuite) TestAPIGPGListKeysCommandFailure(c *C) {
|
||||||
|
script := "#!/bin/sh\n" +
|
||||||
|
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
|
||||||
|
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
|
||||||
|
"echo 'keyring missing'\n" +
|
||||||
|
"exit 1\n" +
|
||||||
|
"fi\n" +
|
||||||
|
"exit 1\n"
|
||||||
|
|
||||||
|
s.withFakeGPG(c, script, func(_ string) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 400)
|
||||||
|
c.Check(response.Body.String(), Matches, `(?s).*failed to list keys: keyring missing.*`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIGPGDeleteKeyCommandFailure tests delete error propagation from gpg
|
||||||
|
func (s *GPGSuite) TestAPIGPGDeleteKeyCommandFailure(c *C) {
|
||||||
|
s.withFakeGPG(c, s.fakeGPGScript(c, "", "", "delete failed"), func(_ string) {
|
||||||
|
body, err := json.Marshal(gpgDeleteKeyParams{
|
||||||
|
Keyring: "trustedkeys.gpg",
|
||||||
|
GpgKeyID: "8B48AD6246925553",
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
|
||||||
|
c.Assert(reqErr, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 400)
|
||||||
|
c.Check(response.Body.String(), Matches, `(?s).*failed to delete key: delete failed.*`)
|
||||||
|
})
|
||||||
|
}
|
||||||
+12
-12
@@ -67,17 +67,17 @@ func (s *MiddlewareSuite) TestJSONMiddleware4xx(c *C) {
|
|||||||
outC := make(chan string)
|
outC := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
io.Copy(&buf, s.logReader)
|
_, _ = io.Copy(&buf, s.logReader)
|
||||||
fmt.Println(buf.String())
|
fmt.Println(buf.String())
|
||||||
outC <- buf.String()
|
outC <- buf.String()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.HTTPRequest(http.MethodGet, "/", nil)
|
s.HTTPRequest(http.MethodGet, "/", nil)
|
||||||
s.logWriter.Close()
|
_ = s.logWriter.Close()
|
||||||
capturedOutput := <-outC
|
capturedOutput := <-outC
|
||||||
|
|
||||||
var jsonMap map[string]interface{}
|
var jsonMap map[string]interface{}
|
||||||
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
if val, ok := jsonMap["level"]; ok {
|
if val, ok := jsonMap["level"]; ok {
|
||||||
c.Check(val, Equals, "warn")
|
c.Check(val, Equals, "warn")
|
||||||
@@ -130,17 +130,17 @@ func (s *MiddlewareSuite) TestJSONMiddleware2xx(c *C) {
|
|||||||
outC := make(chan string)
|
outC := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
io.Copy(&buf, s.logReader)
|
_, _ = io.Copy(&buf, s.logReader)
|
||||||
fmt.Println(buf.String())
|
fmt.Println(buf.String())
|
||||||
outC <- buf.String()
|
outC <- buf.String()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.HTTPRequest(http.MethodGet, "/api/healthy", nil)
|
s.HTTPRequest(http.MethodGet, "/api/healthy", nil)
|
||||||
s.logWriter.Close()
|
_ = s.logWriter.Close()
|
||||||
capturedOutput := <-outC
|
capturedOutput := <-outC
|
||||||
|
|
||||||
var jsonMap map[string]interface{}
|
var jsonMap map[string]interface{}
|
||||||
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
if val, ok := jsonMap["level"]; ok {
|
if val, ok := jsonMap["level"]; ok {
|
||||||
c.Check(val, Equals, "info")
|
c.Check(val, Equals, "info")
|
||||||
@@ -153,17 +153,17 @@ func (s *MiddlewareSuite) TestJSONMiddleware5xx(c *C) {
|
|||||||
outC := make(chan string)
|
outC := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
io.Copy(&buf, s.logReader)
|
_, _ = io.Copy(&buf, s.logReader)
|
||||||
fmt.Println(buf.String())
|
fmt.Println(buf.String())
|
||||||
outC <- buf.String()
|
outC <- buf.String()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.HTTPRequest(http.MethodGet, "/api/ready", nil)
|
s.HTTPRequest(http.MethodGet, "/api/ready", nil)
|
||||||
s.logWriter.Close()
|
_ = s.logWriter.Close()
|
||||||
capturedOutput := <-outC
|
capturedOutput := <-outC
|
||||||
|
|
||||||
var jsonMap map[string]interface{}
|
var jsonMap map[string]interface{}
|
||||||
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
if val, ok := jsonMap["level"]; ok {
|
if val, ok := jsonMap["level"]; ok {
|
||||||
c.Check(val, Equals, "error")
|
c.Check(val, Equals, "error")
|
||||||
@@ -176,17 +176,17 @@ func (s *MiddlewareSuite) TestJSONMiddlewareRaw(c *C) {
|
|||||||
outC := make(chan string)
|
outC := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
io.Copy(&buf, s.logReader)
|
_, _ = io.Copy(&buf, s.logReader)
|
||||||
fmt.Println(buf.String())
|
fmt.Println(buf.String())
|
||||||
outC <- buf.String()
|
outC <- buf.String()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.HTTPRequest(http.MethodGet, "/api/healthy?test=raw", nil)
|
s.HTTPRequest(http.MethodGet, "/api/healthy?test=raw", nil)
|
||||||
s.logWriter.Close()
|
_ = s.logWriter.Close()
|
||||||
capturedOutput := <-outC
|
capturedOutput := <-outC
|
||||||
|
|
||||||
var jsonMap map[string]interface{}
|
var jsonMap map[string]interface{}
|
||||||
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
fmt.Println(capturedOutput)
|
fmt.Println(capturedOutput)
|
||||||
|
|
||||||
|
|||||||
+186
-59
@@ -31,22 +31,61 @@ func getVerifier(keyRings []string) (pgp.Verifier, error) {
|
|||||||
return verifier, nil
|
return verifier, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringSlicesEqual compares two string slices for equality (order matters)
|
||||||
|
func stringSlicesEqual(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniqueStrings returns a new slice with only unique strings from the input, sorted
|
||||||
|
func uniqueStrings(input []string) []string {
|
||||||
|
if len(input) == 0 {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
seen := make(map[string]struct{}, len(input))
|
||||||
|
result := make([]string, 0, len(input))
|
||||||
|
for _, s := range input {
|
||||||
|
if _, ok := seen[s]; !ok {
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// @Summary List Mirrors
|
// @Summary List Mirrors
|
||||||
// @Description **Show list of currently available mirrors**
|
// @Description **Show list of currently available mirrors**
|
||||||
// @Description Each mirror is returned as in “show” API.
|
// @Description Each mirror is returned as in “show” API.
|
||||||
// @Tags Mirrors
|
// @Tags Mirrors
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} deb.RemoteRepo
|
// @Success 200 {array} remoteRepoResponse
|
||||||
// @Router /api/mirrors [get]
|
// @Router /api/mirrors [get]
|
||||||
func apiMirrorsList(c *gin.Context) {
|
func apiMirrorsList(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
|
|
||||||
result := []*deb.RemoteRepo{}
|
result := []remoteRepoResponse{}
|
||||||
collection.ForEach(func(repo *deb.RemoteRepo) error {
|
err := collection.ForEach(func(repo *deb.RemoteRepo) error {
|
||||||
result = append(result, repo)
|
err := collection.LoadComplete(repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, newRemoteRepoResponse(repo))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
@@ -72,6 +111,8 @@ type mirrorCreateParams struct {
|
|||||||
DownloadUdebs bool ` json:"DownloadUdebs"`
|
DownloadUdebs bool ` json:"DownloadUdebs"`
|
||||||
// Set "true" to mirror installer files
|
// Set "true" to mirror installer files
|
||||||
DownloadInstaller bool ` json:"DownloadInstaller"`
|
DownloadInstaller bool ` json:"DownloadInstaller"`
|
||||||
|
// Set "true" to mirror AppStream (DEP-11) metadata
|
||||||
|
DownloadAppStream bool ` json:"DownloadAppStream"`
|
||||||
// Set "true" to include dependencies of matching packages when filtering
|
// Set "true" to include dependencies of matching packages when filtering
|
||||||
FilterWithDeps bool ` json:"FilterWithDeps"`
|
FilterWithDeps bool ` json:"FilterWithDeps"`
|
||||||
// Set "true" to skip if the given components are in the Release file
|
// Set "true" to skip if the given components are in the Release file
|
||||||
@@ -123,7 +164,7 @@ func apiMirrorsCreate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repo, err := deb.NewRemoteRepo(b.Name, b.ArchiveURL, b.Distribution, b.Components, b.Architectures,
|
repo, err := deb.NewRemoteRepo(b.Name, b.ArchiveURL, b.Distribution, b.Components, b.Architectures,
|
||||||
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller)
|
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller, b.DownloadAppStream)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
|
||||||
@@ -232,6 +273,7 @@ func apiMirrorsShow(c *gin.Context) {
|
|||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, repo)
|
c.JSON(200, repo)
|
||||||
@@ -319,7 +361,7 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Request.URL.Query().Get("format") == "details" {
|
if c.Request.URL.Query().Get("format") == "details" {
|
||||||
list.ForEach(func(p *deb.Package) error {
|
_ = list.ForEach(func(p *deb.Package) error {
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -330,29 +372,133 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mirrorEditParams struct {
|
||||||
|
// Package query that is applied to mirror packages
|
||||||
|
Filter *string ` json:"Filter" example:"xserver-xorg"`
|
||||||
|
// Set "true" to include dependencies of matching packages when filtering
|
||||||
|
FilterWithDeps *bool ` json:"FilterWithDeps"`
|
||||||
|
// Set "true" to mirror installer files
|
||||||
|
DownloadInstaller *bool `json:"DownloadInstaller"`
|
||||||
|
// Set "true" to mirror source packages
|
||||||
|
DownloadSources *bool ` json:"DownloadSources"`
|
||||||
|
// Set "true" to mirror udeb files
|
||||||
|
DownloadUdebs *bool ` json:"DownloadUdebs"`
|
||||||
|
// URL of the archive to mirror
|
||||||
|
ArchiveURL *string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
|
||||||
|
// Comma separated list of architectures
|
||||||
|
Architectures *[]string `json:"Architectures" example:"amd64"`
|
||||||
|
// Gpg keyring(s) for verifying Release file if a mirror update is required.
|
||||||
|
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
|
||||||
|
// Set "true" to skip the verification of Release file signatures
|
||||||
|
IgnoreSignatures *bool ` json:"IgnoreSignatures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Edit Mirror
|
||||||
|
// @Description **Edit mirror config**
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Param name path string true "mirror name to edit"
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body mirrorEditParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} deb.RemoteRepo "Mirror was edited successfully"
|
||||||
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
|
// @Failure 404 {object} Error "Mirror not found"
|
||||||
|
// @Failure 409 {object} Error "Aptly db locked"
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/mirrors/{name} [post]
|
||||||
|
func apiMirrorsEdit(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
b mirrorEditParams
|
||||||
|
repo *deb.RemoteRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
|
|
||||||
|
name := c.Params.ByName("name")
|
||||||
|
repo, err = collection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to edit: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.CheckLock()
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 409, fmt.Errorf("unable to edit: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMirror := false
|
||||||
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
|
|
||||||
|
if b.Filter != nil {
|
||||||
|
repo.Filter = *b.Filter
|
||||||
|
}
|
||||||
|
if b.FilterWithDeps != nil {
|
||||||
|
repo.FilterWithDeps = *b.FilterWithDeps
|
||||||
|
}
|
||||||
|
if b.DownloadInstaller != nil {
|
||||||
|
repo.DownloadInstaller = *b.DownloadInstaller
|
||||||
|
}
|
||||||
|
if b.DownloadSources != nil {
|
||||||
|
repo.DownloadSources = *b.DownloadSources
|
||||||
|
}
|
||||||
|
if b.DownloadUdebs != nil {
|
||||||
|
repo.DownloadUdebs = *b.DownloadUdebs
|
||||||
|
}
|
||||||
|
if b.ArchiveURL != nil && *b.ArchiveURL != repo.ArchiveRoot {
|
||||||
|
repo.SetArchiveRoot(*b.ArchiveURL)
|
||||||
|
fetchMirror = true
|
||||||
|
}
|
||||||
|
if b.Architectures != nil {
|
||||||
|
uniqueArchitectures := uniqueStrings(*b.Architectures)
|
||||||
|
if !stringSlicesEqual(uniqueArchitectures, uniqueStrings(repo.Architectures)) {
|
||||||
|
repo.Architectures = uniqueArchitectures
|
||||||
|
fetchMirror = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.IgnoreSignatures != nil {
|
||||||
|
ignoreSignatures = *b.IgnoreSignatures
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.IsFlat() && repo.DownloadUdebs {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to edit: flat mirrors don't support udebs"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetchMirror {
|
||||||
|
verifier, err := getVerifier(b.Keyrings)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = collection.Update(repo)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, repo)
|
||||||
|
}
|
||||||
|
|
||||||
type mirrorUpdateParams struct {
|
type mirrorUpdateParams struct {
|
||||||
// Change mirror name to `Name`
|
// Change mirror name to `Name`
|
||||||
Name string ` json:"Name" example:"mirror1"`
|
Name string ` json:"Name" example:"mirror1"`
|
||||||
// Url of the archive to mirror
|
// Gpg keyring(s) for verifying Release file
|
||||||
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"`
|
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
|
// Set "true" to ignore checksum errors
|
||||||
IgnoreChecksums bool ` json:"IgnoreChecksums"`
|
IgnoreChecksums bool ` json:"IgnoreChecksums"`
|
||||||
// Set "true" to skip the verification of Release file signatures
|
// Set "true" to skip the verification of Release file signatures
|
||||||
@@ -361,6 +507,8 @@ type mirrorUpdateParams struct {
|
|||||||
ForceUpdate bool ` json:"ForceUpdate"`
|
ForceUpdate bool ` json:"ForceUpdate"`
|
||||||
// Set "true" to skip downloading already downloaded packages
|
// Set "true" to skip downloading already downloaded packages
|
||||||
SkipExistingPackages bool ` json:"SkipExistingPackages"`
|
SkipExistingPackages bool ` json:"SkipExistingPackages"`
|
||||||
|
// Set "true" to download only the latest version per package/architecture
|
||||||
|
LatestOnly bool ` json:"LatestOnly"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Update Mirror
|
// @Summary Update Mirror
|
||||||
@@ -394,14 +542,6 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.Name = remote.Name
|
b.Name = remote.Name
|
||||||
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
|
b.IgnoreSignatures = context.Config().GpgDisableVerify
|
||||||
|
|
||||||
log.Info().Msgf("%s: Starting mirror update", b.Name)
|
log.Info().Msgf("%s: Starting mirror update", b.Name)
|
||||||
@@ -418,27 +558,6 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.DownloadUdebs != remote.DownloadUdebs {
|
|
||||||
if remote.IsFlat() && b.DownloadUdebs {
|
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to update: flat mirrors don't support udebs"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.ArchiveURL != "" {
|
|
||||||
remote.SetArchiveRoot(b.ArchiveURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
remote.Name = b.Name
|
|
||||||
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.Keyrings)
|
verifier, err := getVerifier(b.Keyrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
||||||
@@ -461,11 +580,19 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, b.SkipComponentCheck)
|
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remote.DownloadAppStream && !remote.IsFlat() {
|
||||||
|
err = remote.DownloadAppStreamFiles(out, downloader,
|
||||||
|
context.PackagePool(), collectionFactory.ChecksumCollection(nil), b.IgnoreChecksums)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if remote.Filter != "" {
|
if remote.Filter != "" {
|
||||||
var filterQuery deb.PackageQuery
|
var filterQuery deb.PackageQuery
|
||||||
|
|
||||||
@@ -481,7 +608,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
||||||
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
|
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages, b.LatestOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -491,7 +618,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
e := context.ReOpenDatabase()
|
e := context.ReOpenDatabase()
|
||||||
if e == nil {
|
if e == nil {
|
||||||
remote.MarkAsIdle()
|
remote.MarkAsIdle()
|
||||||
collection.Update(remote)
|
_ = collection.Update(remote)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -579,7 +706,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
file, e = os.CreateTemp("", task.File.Filename)
|
file, e = os.CreateTemp("", task.File.Filename)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
task.TempDownPath = file.Name()
|
task.TempDownPath = file.Name()
|
||||||
file.Close()
|
_ = file.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@@ -653,7 +780,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msgf("%s: Finalizing download...", b.Name)
|
log.Info().Msgf("%s: Finalizing download...", b.Name)
|
||||||
remote.FinalizeDownload(collectionFactory, out)
|
_ = remote.FinalizeDownload(collectionFactory, out)
|
||||||
err = collectionFactory.RemoteRepoCollection().Update(remote)
|
err = collectionFactory.RemoteRepoCollection().Update(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
|||||||
+67
-2
@@ -4,12 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MirrorSuite struct {
|
type MirrorSuite struct {
|
||||||
ApiSuite
|
APISuite
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Suite(&MirrorSuite{})
|
var _ = Suite(&MirrorSuite{})
|
||||||
@@ -17,7 +18,10 @@ var _ = Suite(&MirrorSuite{})
|
|||||||
func (s *MirrorSuite) TestGetMirrors(c *C) {
|
func (s *MirrorSuite) TestGetMirrors(c *C) {
|
||||||
response, _ := s.HTTPRequest("GET", "/api/mirrors", nil)
|
response, _ := s.HTTPRequest("GET", "/api/mirrors", nil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
c.Check(response.Body.String(), Equals, "[]")
|
|
||||||
|
var mirrors []map[string]interface{}
|
||||||
|
err := json.Unmarshal(response.Body.Bytes(), &mirrors)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
|
func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
|
||||||
@@ -26,6 +30,21 @@ func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
|
|||||||
c.Check(response.Body.String(), Equals, "{\"error\":\"unable to drop: mirror with name does-not-exist not found\"}")
|
c.Check(response.Body.String(), Equals, "{\"error\":\"unable to drop: mirror with name does-not-exist not found\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MirrorSuite) TestCreateMirrorFlatWithAppStream(c *C) {
|
||||||
|
body, err := json.Marshal(gin.H{
|
||||||
|
"Name": "test-flat-appstream",
|
||||||
|
"ArchiveURL": "http://example.com/repo/",
|
||||||
|
"Distribution": "./",
|
||||||
|
"DownloadAppStream": true,
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("POST", "/api/mirrors", bytes.NewReader(body))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 400)
|
||||||
|
c.Check(response.Body.String(), Matches, ".*AppStream.*flat.*")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MirrorSuite) TestCreateMirror(c *C) {
|
func (s *MirrorSuite) TestCreateMirror(c *C) {
|
||||||
c.ExpectFailure("Need to mock downloads")
|
c.ExpectFailure("Need to mock downloads")
|
||||||
body, err := json.Marshal(gin.H{
|
body, err := json.Marshal(gin.H{
|
||||||
@@ -38,3 +57,49 @@ func (s *MirrorSuite) TestCreateMirror(c *C) {
|
|||||||
c.Check(response.Code, Equals, 400)
|
c.Check(response.Code, Equals, 400)
|
||||||
c.Check(response.Body.String(), Equals, "")
|
c.Check(response.Body.String(), Equals, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MirrorSuite) TestGetMirrorsIncludesNumPackages(c *C) {
|
||||||
|
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
|
||||||
|
|
||||||
|
repo, err := deb.NewRemoteRepo("count-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
err = collection.Add(repo)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
putRawDBValue(c, &s.APISuite, repo.RefKey(), makePackageRefList(c).Encode())
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 200)
|
||||||
|
|
||||||
|
var mirrors []map[string]interface{}
|
||||||
|
err = json.Unmarshal(response.Body.Bytes(), &mirrors)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, mirror := range mirrors {
|
||||||
|
if mirror["Name"] == "count-mirror" {
|
||||||
|
found = true
|
||||||
|
value, ok := mirror["NumPackages"]
|
||||||
|
c.Assert(ok, Equals, true)
|
||||||
|
c.Assert(value, Equals, float64(2))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(found, Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MirrorSuite) TestGetMirrorsReturns500OnCorruptRefList(c *C) {
|
||||||
|
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
|
||||||
|
|
||||||
|
repo, err := deb.NewRemoteRepo("broken-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(collection.Add(repo), IsNil)
|
||||||
|
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 500)
|
||||||
|
c.Assert(response.Body.String(), Matches, ".*unable to show:.*")
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "github.com/aptly-dev/aptly/deb"
|
||||||
|
|
||||||
|
type remoteRepoResponse struct {
|
||||||
|
*deb.RemoteRepo
|
||||||
|
NumPackages int `json:"NumPackages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type localRepoResponse struct {
|
||||||
|
*deb.LocalRepo
|
||||||
|
NumPackages int `json:"NumPackages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type snapshotResponse struct {
|
||||||
|
*deb.Snapshot
|
||||||
|
NumPackages int `json:"NumPackages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteRepoResponse(repo *deb.RemoteRepo) remoteRepoResponse {
|
||||||
|
return remoteRepoResponse{RemoteRepo: repo, NumPackages: repo.NumPackages()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocalRepoResponse(repo *deb.LocalRepo) localRepoResponse {
|
||||||
|
return localRepoResponse{LocalRepo: repo, NumPackages: repo.NumPackages()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSnapshotResponse(snapshot *deb.Snapshot) snapshotResponse {
|
||||||
|
return snapshotResponse{Snapshot: snapshot, NumPackages: snapshot.NumPackages()}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makePackageRefList(c *C) *deb.PackageRefList {
|
||||||
|
list := deb.NewPackageList()
|
||||||
|
c.Assert(list.Add(&deb.Package{Name: "libcount", Version: "1.0", Architecture: "amd64"}), IsNil)
|
||||||
|
c.Assert(list.Add(&deb.Package{Name: "appcount", Version: "2.0", Architecture: "all"}), IsNil)
|
||||||
|
return deb.NewPackageRefListFromPackageList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putRawDBValue(c *C, s *APISuite, key []byte, value []byte) {
|
||||||
|
db, err := s.context.Database()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(db.Put(key, value), IsNil)
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PackagesSuite struct {
|
type PackagesSuite struct {
|
||||||
ApiSuite
|
APISuite
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Suite(&PackagesSuite{})
|
var _ = Suite(&PackagesSuite{})
|
||||||
|
|||||||
+357
-181
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -16,8 +17,8 @@ import (
|
|||||||
type signingParams struct {
|
type signingParams struct {
|
||||||
// Don't sign published repository
|
// Don't sign published repository
|
||||||
Skip bool ` json:"Skip" example:"false"`
|
Skip bool ` json:"Skip" example:"false"`
|
||||||
// GPG key ID to use when signing the release, if not specified default key is used
|
// GPG key ID(s) to use when signing the release, separated by comma, and if not specified, default configured key(s) are used
|
||||||
GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"`
|
GpgKey string ` json:"GpgKey" example:"KEY_ID_a, KEY_ID_b"`
|
||||||
// GPG keyring to use (instead of default)
|
// GPG keyring to use (instead of default)
|
||||||
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
|
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
|
||||||
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
|
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
|
||||||
@@ -41,7 +42,21 @@ func getSigner(options *signingParams) (pgp.Signer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signer := context.GetSigner()
|
signer := context.GetSigner()
|
||||||
signer.SetKey(options.GpgKey)
|
|
||||||
|
var multiGpgKeys []string
|
||||||
|
// REST params have priority over config
|
||||||
|
if options.GpgKey != "" {
|
||||||
|
for _, p := range strings.Split(options.GpgKey, ",") {
|
||||||
|
if t := strings.TrimSpace(p); t != "" {
|
||||||
|
multiGpgKeys = append(multiGpgKeys, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(context.Config().GpgKeys) > 0 {
|
||||||
|
multiGpgKeys = context.Config().GpgKeys
|
||||||
|
}
|
||||||
|
for _, gpgKey := range multiGpgKeys {
|
||||||
|
signer.SetKey(gpgKey)
|
||||||
|
}
|
||||||
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
|
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
|
||||||
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
|
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
|
||||||
|
|
||||||
@@ -110,7 +125,7 @@ func apiPublishList(c *gin.Context) {
|
|||||||
// @Description See also: `aptly publish show`
|
// @Description See also: `aptly publish show`
|
||||||
// @Tags Publish
|
// @Tags Publish
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambigious in URLs"
|
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambiguous in URLs"
|
||||||
// @Param distribution path string true "distribution name"
|
// @Param distribution path string true "distribution name"
|
||||||
// @Success 200 {object} deb.PublishedRepo
|
// @Success 200 {object} deb.PublishedRepo
|
||||||
// @Failure 404 {object} Error "Published repository not found"
|
// @Failure 404 {object} Error "Published repository not found"
|
||||||
@@ -168,8 +183,12 @@ type publishedRepoCreateParams struct {
|
|||||||
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
||||||
// Provide index files by hash
|
// Provide index files by hash
|
||||||
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
||||||
|
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file.
|
||||||
|
SignedBy *string ` json:"SignedBy" example:""`
|
||||||
// Enable multiple packages with the same filename in different distributions
|
// Enable multiple packages with the same filename in different distributions
|
||||||
MultiDist *bool ` json:"MultiDist" example:"false"`
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
||||||
|
// Version of the release
|
||||||
|
Version string ` json:"Version" example:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Create Published Repository
|
// @Summary Create Published Repository
|
||||||
@@ -280,11 +299,25 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
multiDist = *b.MultiDist
|
multiDist = *b.MultiDist
|
||||||
}
|
}
|
||||||
|
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
// Pre-register the published repo key in resources so that concurrent
|
||||||
|
// POST requests for the same prefix/distribution are serialized by the
|
||||||
|
// task queue rather than racing on CheckDuplicate + Add.
|
||||||
|
if b.Distribution != "" {
|
||||||
|
storagePrefix := prefix
|
||||||
|
if storage != "" {
|
||||||
|
storagePrefix = storage + ":" + prefix
|
||||||
|
}
|
||||||
|
resources = append(resources, "U"+storagePrefix+">>"+b.Distribution)
|
||||||
|
} else {
|
||||||
|
log.Printf("distribution not specified for publish to prefix '%s' - unable to lock ", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"",
|
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, `", "`))
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
taskDetail := task.PublishDetail{
|
taskDetail := task.PublishDetail{
|
||||||
Detail: detail,
|
Detail: detail,
|
||||||
}
|
}
|
||||||
@@ -296,10 +329,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
for _, source := range sources {
|
for _, source := range sources {
|
||||||
switch s := source.(type) {
|
switch s := source.(type) {
|
||||||
case *deb.Snapshot:
|
case *deb.Snapshot:
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
err = snapshotCollection.LoadComplete(s)
|
err = snapshotCollection.LoadComplete(s)
|
||||||
case *deb.LocalRepo:
|
case *deb.LocalRepo:
|
||||||
localCollection := collectionFactory.LocalRepoCollection()
|
localCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
err = localCollection.LoadComplete(s)
|
err = localCollection.LoadComplete(s)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unexpected type for source: %T", source)
|
err = fmt.Errorf("unexpected type for source: %T", source)
|
||||||
@@ -309,13 +342,11 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, multiDist)
|
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, taskCollectionFactory, multiDist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(published.Key()))
|
|
||||||
|
|
||||||
if b.Origin != "" {
|
if b.Origin != "" {
|
||||||
published.Origin = b.Origin
|
published.Origin = b.Origin
|
||||||
}
|
}
|
||||||
@@ -341,18 +372,26 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
published.AcquireByHash = *b.AcquireByHash
|
published.AcquireByHash = *b.AcquireByHash
|
||||||
}
|
}
|
||||||
|
|
||||||
duplicate := collection.CheckDuplicate(published)
|
if b.SignedBy != nil {
|
||||||
|
published.SignedBy = *b.SignedBy
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Version != "" {
|
||||||
|
published.Version = b.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicate := taskCollection.CheckDuplicate(published)
|
||||||
if duplicate != nil {
|
if duplicate != nil {
|
||||||
collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
_ = taskCollectionFactory.PublishedRepoCollection().LoadComplete(duplicate, taskCollectionFactory)
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
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, context.SkelPath())
|
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Add(published)
|
err = taskCollection.Add(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -376,8 +415,16 @@ type publishedRepoUpdateSwitchParams struct {
|
|||||||
Snapshots []sourceParams ` json:"Snapshots"`
|
Snapshots []sourceParams ` json:"Snapshots"`
|
||||||
// Provide index files by hash
|
// Provide index files by hash
|
||||||
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
||||||
|
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file
|
||||||
|
SignedBy *string ` json:"SignedBy" example:""`
|
||||||
// Enable multiple packages with the same filename in different distributions
|
// Enable multiple packages with the same filename in different distributions
|
||||||
MultiDist *bool ` json:"MultiDist" example:"false"`
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
||||||
|
// Value of Label: field in published repository stanza
|
||||||
|
Label *string ` json:"Label" example:"Debian"`
|
||||||
|
// Value of Origin: field in published repository stanza
|
||||||
|
Origin *string ` json:"Origin" example:"Debian"`
|
||||||
|
// Version of the release: Optional
|
||||||
|
Version *string ` json:"Version" example:"13.3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Update Published Repository
|
// @Summary Update Published Repository
|
||||||
@@ -393,7 +440,6 @@ type publishedRepoUpdateSwitchParams struct {
|
|||||||
// @Description
|
// @Description
|
||||||
// @Description See also: `aptly publish update` / `aptly publish switch`
|
// @Description See also: `aptly publish update` / `aptly publish switch`
|
||||||
// @Tags Publish
|
// @Tags Publish
|
||||||
// @Produce json
|
|
||||||
// @Param prefix path string true "publishing prefix"
|
// @Param prefix path string true "publishing prefix"
|
||||||
// @Param distribution path string true "distribution name"
|
// @Param distribution path string true "distribution name"
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
@@ -425,6 +471,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -432,46 +479,76 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resources := []string{string(published.Key())}
|
||||||
|
|
||||||
if published.SourceKind == deb.SourceLocalRepo {
|
if published.SourceKind == deb.SourceLocalRepo {
|
||||||
if len(b.Snapshots) > 0 {
|
if len(b.Snapshots) > 0 {
|
||||||
AbortWithJSONError(c, http.StatusBadRequest, 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
|
return
|
||||||
}
|
}
|
||||||
} else if published.SourceKind == deb.SourceSnapshot {
|
for _, uuid := range published.Sources {
|
||||||
for _, snapshotInfo := range b.Snapshots {
|
repo, err2 := localRepoCollection.ByUUID(uuid)
|
||||||
_, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, err2)
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resources = append(resources, string(repo.Key()))
|
||||||
|
}
|
||||||
|
} else if published.SourceKind == deb.SourceSnapshot {
|
||||||
|
for _, snapshotInfo := range b.Snapshots {
|
||||||
|
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
||||||
|
if err2 != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources = append(resources, string(snapshot.ResourceKey()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.SkipContents != nil {
|
// Field mutations and fresh DB load are deferred to inside the task so
|
||||||
published.SkipContents = *b.SkipContents
|
// they always operate on a consistent state after the lock is held.
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply field mutations on the freshly loaded object.
|
||||||
|
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.SignedBy != nil {
|
||||||
|
published.SignedBy = *b.SignedBy
|
||||||
|
}
|
||||||
|
if b.MultiDist != nil {
|
||||||
|
published.MultiDist = *b.MultiDist
|
||||||
|
}
|
||||||
|
if b.Label != nil {
|
||||||
|
published.Label = *b.Label
|
||||||
|
}
|
||||||
|
if b.Origin != nil {
|
||||||
|
published.Origin = *b.Origin
|
||||||
|
}
|
||||||
|
if b.Version != nil {
|
||||||
|
published.Version = *b.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
revision := published.ObtainRevision()
|
revision := published.ObtainRevision()
|
||||||
@@ -485,17 +562,17 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := published.Update(collectionFactory, out)
|
result, err := published.Update(taskCollectionFactory, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err)
|
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())
|
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Update(published)
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -503,7 +580,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||||
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
||||||
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
||||||
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -553,8 +630,11 @@ func apiPublishDrop(c *gin.Context) {
|
|||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Delete published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := collection.Remove(context, storage, prefix, distribution,
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
collectionFactory, out, force, skipCleanup)
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
err := taskCollection.Remove(context, storage, prefix, distribution,
|
||||||
|
taskCollectionFactory, out, force, skipCleanup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
||||||
}
|
}
|
||||||
@@ -590,43 +670,52 @@ func apiPublishAddSource(c *gin.Context) {
|
|||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||||
|
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly (no LoadComplete) to verify existence and obtain the
|
||||||
|
// resource key and task name. The actual mutation is performed inside
|
||||||
|
// the task on a freshly loaded copy to prevent lost-update races.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err))
|
||||||
return
|
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())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to create: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := revision.Sources
|
||||||
|
|
||||||
|
component := b.Component
|
||||||
|
name := b.Name
|
||||||
|
|
||||||
|
_, exists := sources[component]
|
||||||
|
if exists {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("unable to create: Component '%s' already exists", component)
|
||||||
|
}
|
||||||
|
|
||||||
|
sources[component] = name
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -708,39 +797,48 @@ func apiPublishSetSources(c *gin.Context) {
|
|||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||||
|
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and mutation happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
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())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -773,24 +871,33 @@ func apiPublishDropChanges(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and DropRevision happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
||||||
return
|
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())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
published.DropRevision()
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -826,51 +933,58 @@ func apiPublishUpdateSource(c *gin.Context) {
|
|||||||
param := slashEscape(c.Params.ByName("prefix"))
|
param := slashEscape(c.Params.ByName("prefix"))
|
||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||||
component := slashEscape(c.Params.ByName("component"))
|
urlComponent := slashEscape(c.Params.ByName("component"))
|
||||||
|
|
||||||
|
// Default component to the URL path segment; the body may rename it.
|
||||||
|
b.Component = urlComponent
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and mutation happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
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())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := revision.Sources
|
||||||
|
|
||||||
|
_, exists := sources[urlComponent]
|
||||||
|
if !exists {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: Component '%s' does not exist", urlComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Component != urlComponent {
|
||||||
|
delete(sources, urlComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
newComponent := b.Component
|
||||||
|
name := b.Name
|
||||||
|
sources[newComponent] = name
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -907,33 +1021,41 @@ func apiPublishRemoveSource(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and mutation happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
||||||
return
|
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())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := revision.Sources
|
||||||
|
|
||||||
|
_, exists := sources[component]
|
||||||
|
if !exists {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: Component '%s' does not exist", component)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(sources, component)
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -955,8 +1077,16 @@ type publishedRepoUpdateParams struct {
|
|||||||
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
||||||
// Provide index files by hash
|
// Provide index files by hash
|
||||||
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
||||||
|
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file
|
||||||
|
SignedBy *string ` json:"SignedBy" example:""`
|
||||||
// Enable multiple packages with the same filename in different distributions
|
// Enable multiple packages with the same filename in different distributions
|
||||||
MultiDist *bool ` json:"MultiDist" example:"false"`
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
||||||
|
// Value of Label: field in published repository stanza
|
||||||
|
Label *string ` json:"Label" example:"Debian"`
|
||||||
|
// Value of Origin: field in published repository stanza
|
||||||
|
Origin *string ` json:"Origin" example:"Debian"`
|
||||||
|
// Version of the release: Optional
|
||||||
|
Version *string ` json:"Version" example:"13.3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Update Published Repository
|
// @Summary Update Published Repository
|
||||||
@@ -997,48 +1127,94 @@ func apiPublishUpdate(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and field mutations happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
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())}
|
resources := []string{string(published.Key())}
|
||||||
|
|
||||||
|
// Lock source repos / snapshots the same way apiPublishUpdateSwitch does,
|
||||||
|
// because published.Update() reads from them and concurrent modification
|
||||||
|
// would produce an inconsistent view.
|
||||||
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
if published.SourceKind == deb.SourceLocalRepo {
|
||||||
|
for _, uuid := range published.Sources {
|
||||||
|
repo, err2 := localRepoCollection.ByUUID(uuid)
|
||||||
|
if err2 != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources = append(resources, string(repo.Key()))
|
||||||
|
}
|
||||||
|
} else if published.SourceKind == deb.SourceSnapshot {
|
||||||
|
for _, uuid := range published.Sources {
|
||||||
|
snapshot, err2 := snapshotCollection.ByUUID(uuid)
|
||||||
|
if err2 != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources = append(resources, string(snapshot.ResourceKey()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
result, err := published.Update(collectionFactory, out)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
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())
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Update(published)
|
// Apply field mutations on the freshly loaded object.
|
||||||
|
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.SignedBy != nil {
|
||||||
|
published.SignedBy = *b.SignedBy
|
||||||
|
}
|
||||||
|
if b.MultiDist != nil {
|
||||||
|
published.MultiDist = *b.MultiDist
|
||||||
|
}
|
||||||
|
if b.Label != nil {
|
||||||
|
published.Label = *b.Label
|
||||||
|
}
|
||||||
|
if b.Origin != nil {
|
||||||
|
published.Origin = *b.Origin
|
||||||
|
}
|
||||||
|
if b.Version != nil {
|
||||||
|
published.Version = *b.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := published.Update(taskCollectionFactory, 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, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
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.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -1046,7 +1222,7 @@ func apiPublishUpdate(c *gin.Context) {
|
|||||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||||
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
||||||
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
||||||
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-44
@@ -24,19 +24,19 @@ import (
|
|||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce html
|
// @Produce html
|
||||||
// @Success 200 {object} string "HTML"
|
// @Success 200 {object} string "HTML"
|
||||||
// @Router /api/repos [get]
|
// @Router /repos [get]
|
||||||
func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc {
|
func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
c.Writer.Flush()
|
c.Writer.Flush()
|
||||||
c.Writer.WriteString("<pre>\n")
|
_, _ = c.Writer.WriteString("<pre>\n")
|
||||||
if len(localRepos) == 0 {
|
if len(localRepos) == 0 {
|
||||||
c.Writer.WriteString("<a href=\"-/\">default</a>\n")
|
_, _ = c.Writer.WriteString("<a href=\"-/\">default</a>\n")
|
||||||
}
|
}
|
||||||
for publishPrefix := range localRepos {
|
for publishPrefix := range localRepos {
|
||||||
c.Writer.WriteString(fmt.Sprintf("<a href=\"%[1]s/\">%[1]s</a>\n", publishPrefix))
|
_, _ = c.Writer.WriteString(fmt.Sprintf("<a href=\"%[1]s/\">%[1]s</a>\n", publishPrefix))
|
||||||
}
|
}
|
||||||
c.Writer.WriteString("</pre>")
|
_, _ = c.Writer.WriteString("</pre>")
|
||||||
c.Writer.Flush()
|
c.Writer.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.H
|
|||||||
// @Param pkgPath path string true "Package Path" allowReserved=true
|
// @Param pkgPath path string true "Package Path" allowReserved=true
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 ""
|
// @Success 200 ""
|
||||||
// @Router /api/{storage}/{pkgPath} [get]
|
// @Router /repos/{storage}/{pkgPath} [get]
|
||||||
func reposServeInAPIMode(c *gin.Context) {
|
func reposServeInAPIMode(c *gin.Context) {
|
||||||
pkgpath := c.Param("pkgPath")
|
pkgpath := c.Param("pkgPath")
|
||||||
|
|
||||||
@@ -69,17 +69,26 @@ func reposServeInAPIMode(c *gin.Context) {
|
|||||||
// @Description Each repo is returned as in “show” API.
|
// @Description Each repo is returned as in “show” API.
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} deb.LocalRepo
|
// @Success 200 {array} localRepoResponse
|
||||||
// @Router /api/repos [get]
|
// @Router /api/repos [get]
|
||||||
func apiReposList(c *gin.Context) {
|
func apiReposList(c *gin.Context) {
|
||||||
result := []*deb.LocalRepo{}
|
result := []localRepoResponse{}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
collection.ForEach(func(r *deb.LocalRepo) error {
|
err := collection.ForEach(func(r *deb.LocalRepo) error {
|
||||||
result = append(result, r)
|
err := collection.LoadComplete(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, newLocalRepoResponse(r))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
@@ -93,7 +102,7 @@ type repoCreateParams struct {
|
|||||||
DefaultDistribution string ` json:"DefaultDistribution" example:"stable"`
|
DefaultDistribution string ` json:"DefaultDistribution" example:"stable"`
|
||||||
// Default component when publishing from this local repo
|
// Default component when publishing from this local repo
|
||||||
DefaultComponent string ` json:"DefaultComponent" example:"main"`
|
DefaultComponent string ` json:"DefaultComponent" example:"main"`
|
||||||
// Snapshot name to create repoitory from (optional)
|
// Snapshot name to create repository from (optional)
|
||||||
FromSnapshot string ` json:"FromSnapshot" example:""`
|
FromSnapshot string ` json:"FromSnapshot" example:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,9 +116,9 @@ type repoCreateParams struct {
|
|||||||
// @Description {"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}
|
// @Description {"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}
|
||||||
// @Description ```
|
// @Description ```
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
|
||||||
// @Consume json
|
// @Consume json
|
||||||
// @Param request body repoCreateParams true "Parameters"
|
// @Param request body repoCreateParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
// @Success 201 {object} deb.LocalRepo
|
// @Success 201 {object} deb.LocalRepo
|
||||||
// @Failure 404 {object} Error "Source snapshot not found"
|
// @Failure 404 {object} Error "Source snapshot not found"
|
||||||
// @Failure 409 {object} Error "Local repo already exists"
|
// @Failure 409 {object} Error "Local repo already exists"
|
||||||
@@ -171,15 +180,17 @@ type reposEditParams struct {
|
|||||||
Comment *string ` json:"Comment" example:"example repo"`
|
Comment *string ` json:"Comment" example:"example repo"`
|
||||||
// Change Default Distribution for publishing
|
// Change Default Distribution for publishing
|
||||||
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
|
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
|
||||||
// Change Devault Component for publishing
|
// Change Default Component for publishing
|
||||||
DefaultComponent *string ` json:"DefaultComponent" example:""`
|
DefaultComponent *string ` json:"DefaultComponent" example:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Update Repository
|
// @Summary Update Repository
|
||||||
// @Description **Update local repository meta information**
|
// @Description **Update local repository meta information**
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
|
// @Consume json
|
||||||
// @Param request body reposEditParams true "Parameters"
|
// @Param request body reposEditParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} deb.LocalRepo "msg"
|
// @Success 200 {object} deb.LocalRepo "msg"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
// @Failure 500 {object} Error "Internal Server Error"
|
// @Failure 500 {object} Error "Internal Server Error"
|
||||||
@@ -193,17 +204,18 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
name := c.Params.ByName("name")
|
||||||
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Name != nil {
|
if b.Name != nil && *b.Name != name {
|
||||||
_, err := collection.ByName(*b.Name)
|
_, err := collection.ByName(*b.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// already exists
|
// already exists
|
||||||
AbortWithJSONError(c, 404, err)
|
AbortWithJSONError(c, 404, fmt.Errorf("local repo with name %q already exists", *b.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repo.Name = *b.Name
|
repo.Name = *b.Name
|
||||||
@@ -231,8 +243,8 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
// @Summary Get Repository Info
|
// @Summary Get Repository Info
|
||||||
// @Description Returns basic information about local repository.
|
// @Description Returns basic information about local repository.
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
|
||||||
// @Param name path string true "Repository name"
|
// @Param name path string true "Repository name"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} deb.LocalRepo
|
// @Success 200 {object} deb.LocalRepo
|
||||||
// @Failure 404 {object} Error "Repository not found"
|
// @Failure 404 {object} Error "Repository not found"
|
||||||
// @Router /api/repos/{name} [get]
|
// @Router /api/repos/{name} [get]
|
||||||
@@ -254,9 +266,10 @@ func apiReposShow(c *gin.Context) {
|
|||||||
// @Description Cannot drop repos that are published.
|
// @Description Cannot drop repos that are published.
|
||||||
// @Description Needs force=1 to drop repos used as source by other repos.
|
// @Description Needs force=1 to drop repos used as source by other repos.
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
// @Param force query int false "force: 1 to enable"
|
// @Param force query int false "force: 1 to enable"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} task.ProcessReturnValue "Repo object"
|
// @Success 200 {object} task.ProcessReturnValue "Repo object"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
// @Failure 404 {object} Error "Repo Conflict"
|
// @Failure 404 {object} Error "Repo Conflict"
|
||||||
@@ -306,12 +319,12 @@ func apiReposDrop(c *gin.Context) {
|
|||||||
// @Description ["Pi386 aptly 0.8 966561016b44ed80"]
|
// @Description ["Pi386 aptly 0.8 966561016b44ed80"]
|
||||||
// @Description ```
|
// @Description ```
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
// @Param name path string true "Snapshot to search"
|
|
||||||
// @Param q query string true "Package query (e.g Name%20(~%20matlab))"
|
// @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 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 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"
|
// @Param maximumVersion query string true "Set to 1 to only return the highest version for each package name"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} string "msg"
|
// @Success 200 {object} string "msg"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
// @Failure 404 {object} Error "Internal Server Error"
|
// @Failure 404 {object} Error "Internal Server Error"
|
||||||
@@ -406,9 +419,11 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
|||||||
// @Description
|
// @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.
|
// @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
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
|
// @Consume json
|
||||||
// @Param request body reposPackagesAddDeleteParams true "Parameters"
|
// @Param request body reposPackagesAddDeleteParams true "Parameters"
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} string "msg"
|
// @Success 200 {object} string "msg"
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
@@ -426,9 +441,11 @@ func apiReposPackagesAdd(c *gin.Context) {
|
|||||||
// @Description
|
// @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.
|
// @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
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
// @Param request body reposPackagesAddDeleteParams true "Parameters"
|
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body reposPackagesAddDeleteParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} string "msg"
|
// @Success 200 {object} string "msg"
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
@@ -449,7 +466,7 @@ func apiReposPackagesDelete(c *gin.Context) {
|
|||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Param name path string true "Repository name"
|
// @Param name path string true "Repository name"
|
||||||
// @Param dir path string true "Directory of packages"
|
// @Param dir path string true "Directory of packages"
|
||||||
// @Param file path string false "Filename (optional)"
|
// @Param file path string true "Filename"
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {string} string "OK"
|
// @Success 200 {string} string "OK"
|
||||||
@@ -570,7 +587,7 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// atempt to remove dir, if it fails, that's fine: probably it's not empty
|
// atempt to remove dir, if it fails, that's fine: probably it's not empty
|
||||||
os.Remove(filepath.Join(context.UploadPath(), dirParam))
|
_ = os.Remove(filepath.Join(context.UploadPath(), dirParam))
|
||||||
}
|
}
|
||||||
|
|
||||||
if failedFiles == nil {
|
if failedFiles == nil {
|
||||||
@@ -607,11 +624,11 @@ type reposCopyPackageParams struct {
|
|||||||
// @Summary Copy Package
|
// @Summary Copy Package
|
||||||
// @Description Copies a package from a source to destination repository
|
// @Description Copies a package from a source to destination repository
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Destination repo"
|
||||||
// @Param name path string true "Source repo"
|
// @Param src path string true "Source repo"
|
||||||
// @Param src path string true "Destination repo"
|
|
||||||
// @Param file path string true "File/packages to copy"
|
// @Param file path string true "File/packages to copy"
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} task.ProcessReturnValue "msg"
|
// @Success 200 {object} task.ProcessReturnValue "msg"
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
@@ -762,12 +779,15 @@ func apiReposCopyPackage(c *gin.Context) {
|
|||||||
// @Summary Include File from Directory
|
// @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.
|
// @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
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
|
// @Param dir path string true "Directory of packages"
|
||||||
|
// @Param file path string true "File/packages to include"
|
||||||
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
|
// @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 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 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 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"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} string "msg"
|
// @Success 200 {object} string "msg"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
// @Router /api/repos/{name}/include/{dir}/{file} [post]
|
// @Router /api/repos/{name}/include/{dir}/{file} [post]
|
||||||
@@ -776,26 +796,22 @@ func apiReposIncludePackageFromFile(c *gin.Context) {
|
|||||||
apiReposIncludePackageFromDir(c)
|
apiReposIncludePackageFromDir(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
type reposIncludePackageFromDirReport struct {
|
|
||||||
Warnings []string
|
|
||||||
Added []string
|
|
||||||
Deleted []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type reposIncludePackageFromDirResponse struct {
|
type reposIncludePackageFromDirResponse struct {
|
||||||
Report reposIncludePackageFromDirReport
|
Report *aptly.RecordingResultReporter
|
||||||
FailedFiles []string
|
FailedFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Include Directory
|
// @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.
|
// @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
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Param name path string true "Repository name"
|
||||||
|
// @Param dir path string true "Directory of packages"
|
||||||
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
|
// @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 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 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 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"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
|
// @Produce json
|
||||||
// @Success 200 {object} reposIncludePackageFromDirResponse "Response"
|
// @Success 200 {object} reposIncludePackageFromDirResponse "Response"
|
||||||
// @Failure 404 {object} Error "Not Found"
|
// @Failure 404 {object} Error "Not Found"
|
||||||
// @Router /api/repos/{name}/include/{dir} [post]
|
// @Router /api/repos/{name}/include/{dir} [post]
|
||||||
@@ -836,7 +852,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resources []string
|
var resources []string
|
||||||
if len(repoTemplate.Tree.Root.Nodes) > 1 {
|
if len(repoTemplate.Root.Nodes) > 1 {
|
||||||
resources = append(resources, task.AllLocalReposResourcesKey)
|
resources = append(resources, task.AllLocalReposResourcesKey)
|
||||||
} else {
|
} else {
|
||||||
// repo template string is simple text so only use resource key of specific repository
|
// repo template string is simple text so only use resource key of specific repository
|
||||||
@@ -876,7 +892,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
|
|
||||||
if !noRemoveFiles {
|
if !noRemoveFiles {
|
||||||
// atempt to remove dir, if it fails, that's fine: probably it's not empty
|
// atempt to remove dir, if it fails, that's fine: probably it's not empty
|
||||||
os.Remove(filepath.Join(context.UploadPath(), dirParam))
|
_ = os.Remove(filepath.Join(context.UploadPath(), dirParam))
|
||||||
}
|
}
|
||||||
|
|
||||||
if failedFiles == nil {
|
if failedFiles == nil {
|
||||||
@@ -896,9 +912,10 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", "))
|
out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{
|
ret := reposIncludePackageFromDirResponse{
|
||||||
"Report": reporter,
|
Report: reporter,
|
||||||
"FailedFiles": failedFiles,
|
FailedFiles: failedFiles,
|
||||||
}}, nil
|
}
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: ret}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReposSuite struct {
|
||||||
|
APISuite
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&ReposSuite{})
|
||||||
|
|
||||||
|
func (s *ReposSuite) TestGetReposIncludesNumPackages(c *C) {
|
||||||
|
collection := s.context.NewCollectionFactory().LocalRepoCollection()
|
||||||
|
repo := deb.NewLocalRepo("count-repo-list", "")
|
||||||
|
repo.UpdateRefList(makePackageRefList(c))
|
||||||
|
c.Assert(collection.Add(repo), IsNil)
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/repos", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 200)
|
||||||
|
|
||||||
|
var repos []map[string]interface{}
|
||||||
|
err = json.Unmarshal(response.Body.Bytes(), &repos)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, repo := range repos {
|
||||||
|
if repo["Name"] == "count-repo-list" {
|
||||||
|
found = true
|
||||||
|
value, ok := repo["NumPackages"]
|
||||||
|
c.Assert(ok, Equals, true)
|
||||||
|
c.Assert(value, Equals, float64(2))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(found, Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReposSuite) TestGetReposReturns500OnCorruptRefList(c *C) {
|
||||||
|
body, err := json.Marshal(gin.H{"Name": "broken-repo-list"})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 201)
|
||||||
|
|
||||||
|
collection := s.context.NewCollectionFactory().LocalRepoCollection()
|
||||||
|
repo, err := collection.ByName("broken-repo-list")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
|
||||||
|
|
||||||
|
response, err = s.HTTPRequest("GET", "/api/repos", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 500)
|
||||||
|
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
|
||||||
|
}
|
||||||
+10
-6
@@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
@@ -19,6 +18,12 @@ import (
|
|||||||
|
|
||||||
var context *ctx.AptlyContext
|
var context *ctx.AptlyContext
|
||||||
|
|
||||||
|
// @Summary Get Metrics
|
||||||
|
// @Description **Get Prometheus Metrics**
|
||||||
|
// @Tags Status
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Success 200 {string} string Metrics
|
||||||
|
// @Router /api/metrics [get]
|
||||||
func apiMetricsGet() gin.HandlerFunc {
|
func apiMetricsGet() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
countPackagesByRepos()
|
countPackagesByRepos()
|
||||||
@@ -56,13 +61,9 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
router.UseRawPath = true
|
router.UseRawPath = true
|
||||||
|
|
||||||
if c.Config().LogFormat == "json" {
|
if c.Config().LogFormat == "json" {
|
||||||
c.StructuredLogging(true)
|
|
||||||
utils.SetupJSONLogger(c.Config().LogLevel, os.Stdout)
|
|
||||||
gin.DefaultWriter = utils.LogWriter{Logger: log.Logger}
|
gin.DefaultWriter = utils.LogWriter{Logger: log.Logger}
|
||||||
router.Use(JSONLogger())
|
router.Use(JSONLogger())
|
||||||
} else {
|
} else {
|
||||||
c.StructuredLogging(false)
|
|
||||||
utils.SetupDefaultLogger(c.Config().LogLevel)
|
|
||||||
router.Use(gin.Logger())
|
router.Use(gin.Logger())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,12 +164,15 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
api.GET("/mirrors/:name", apiMirrorsShow)
|
api.GET("/mirrors/:name", apiMirrorsShow)
|
||||||
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
||||||
api.POST("/mirrors", apiMirrorsCreate)
|
api.POST("/mirrors", apiMirrorsCreate)
|
||||||
|
api.POST("/mirrors/:name", apiMirrorsEdit)
|
||||||
api.PUT("/mirrors/:name", apiMirrorsUpdate)
|
api.PUT("/mirrors/:name", apiMirrorsUpdate)
|
||||||
api.DELETE("/mirrors/:name", apiMirrorsDrop)
|
api.DELETE("/mirrors/:name", apiMirrorsDrop)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
api.GET("/gpg/keys", apiGPGListKeys)
|
||||||
api.POST("/gpg/key", apiGPGAddKey)
|
api.POST("/gpg/key", apiGPGAddKey)
|
||||||
|
api.DELETE("/gpg/key", apiGPGDeleteKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -220,7 +224,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
api.GET("/graph.:ext", apiGraph)
|
api.GET("/graph.:ext", apiGraph)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
api.POST("/db/cleanup", apiDbCleanup)
|
api.POST("/db/cleanup", apiDBCleanup)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
api.GET("/tasks", apiTasksList)
|
api.GET("/tasks", apiTasksList)
|
||||||
|
|||||||
+17
-9
@@ -20,7 +20,7 @@ import (
|
|||||||
// @Description Each snapshot is returned as in “show” API.
|
// @Description Each snapshot is returned as in “show” API.
|
||||||
// @Tags Snapshots
|
// @Tags Snapshots
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} deb.Snapshot
|
// @Success 200 {array} snapshotResponse
|
||||||
// @Router /api/snapshots [get]
|
// @Router /api/snapshots [get]
|
||||||
func apiSnapshotsList(c *gin.Context) {
|
func apiSnapshotsList(c *gin.Context) {
|
||||||
SortMethodString := c.Request.URL.Query().Get("sort")
|
SortMethodString := c.Request.URL.Query().Get("sort")
|
||||||
@@ -32,11 +32,20 @@ func apiSnapshotsList(c *gin.Context) {
|
|||||||
SortMethodString = "name"
|
SortMethodString = "name"
|
||||||
}
|
}
|
||||||
|
|
||||||
result := []*deb.Snapshot{}
|
result := []snapshotResponse{}
|
||||||
collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
|
err := collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
|
||||||
result = append(result, snapshot)
|
err := collection.LoadComplete(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, newSnapshotResponse(snapshot))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
@@ -217,10 +226,9 @@ type snapshotsCreateFromRepositoryParams struct {
|
|||||||
// @Summary Snapshot Repository
|
// @Summary Snapshot Repository
|
||||||
// @Description **Create a snapshot of a repository by name**
|
// @Description **Create a snapshot of a repository by name**
|
||||||
// @Tags Snapshots
|
// @Tags Snapshots
|
||||||
// @Param name path string true "Repository name"
|
|
||||||
// @Consume json
|
// @Consume json
|
||||||
// @Param request body snapshotsCreateFromRepositoryParams true "Parameters"
|
// @Param request body snapshotsCreateFromRepositoryParams true "Parameters"
|
||||||
// @Param name path string true "Name of the snapshot"
|
// @Param name path string true "Repository name"
|
||||||
// @Param _async query bool false "Run in background and return task object"
|
// @Param _async query bool false "Run in background and return task object"
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 201 {object} deb.Snapshot "Created snapshot object"
|
// @Success 201 {object} deb.Snapshot "Created snapshot object"
|
||||||
@@ -555,7 +563,7 @@ func apiSnapshotsMerge(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(body.Sources) < 1 {
|
if len(body.Sources) < 1 {
|
||||||
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("At least one source snapshot is required"))
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("minimum one source snapshot is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,7 +773,7 @@ func apiSnapshotsPull(c *gin.Context) {
|
|||||||
addedPackages := []string{}
|
addedPackages := []string{}
|
||||||
alreadySeen := map[string]bool{}
|
alreadySeen := map[string]bool{}
|
||||||
|
|
||||||
destinationPackageList.ForEachIndexed(func(pkg *deb.Package) error {
|
_ = destinationPackageList.ForEachIndexed(func(pkg *deb.Package) error {
|
||||||
key := pkg.Architecture + "_" + pkg.Name
|
key := pkg.Architecture + "_" + pkg.Name
|
||||||
_, seen := alreadySeen[key]
|
_, seen := alreadySeen[key]
|
||||||
|
|
||||||
@@ -781,7 +789,7 @@ func apiSnapshotsPull(c *gin.Context) {
|
|||||||
|
|
||||||
// If !allMatches, add only first matching name-arch package
|
// If !allMatches, add only first matching name-arch package
|
||||||
if !seen || allMatches {
|
if !seen || allMatches {
|
||||||
toPackageList.Add(pkg)
|
_ = toPackageList.Add(pkg)
|
||||||
addedPackages = append(addedPackages, pkg.String())
|
addedPackages = append(addedPackages, pkg.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnapshotsSuite struct {
|
||||||
|
APISuite
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&SnapshotsSuite{})
|
||||||
|
|
||||||
|
func (s *SnapshotsSuite) TestGetSnapshotsIncludesNumPackages(c *C) {
|
||||||
|
collection := s.context.NewCollectionFactory().SnapshotCollection()
|
||||||
|
snapshot := deb.NewSnapshotFromRefList("count-snapshot-list", nil, makePackageRefList(c), "")
|
||||||
|
c.Assert(collection.Add(snapshot), IsNil)
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 200)
|
||||||
|
|
||||||
|
var snapshots []map[string]interface{}
|
||||||
|
err = json.Unmarshal(response.Body.Bytes(), &snapshots)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, snapshot := range snapshots {
|
||||||
|
if snapshot["Name"] == "count-snapshot-list" {
|
||||||
|
found = true
|
||||||
|
value, ok := snapshot["NumPackages"]
|
||||||
|
c.Assert(ok, Equals, true)
|
||||||
|
c.Assert(value, Equals, float64(2))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(found, Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SnapshotsSuite) TestGetSnapshotsReturns500OnCorruptRefList(c *C) {
|
||||||
|
collection := s.context.NewCollectionFactory().SnapshotCollection()
|
||||||
|
snapshot := deb.NewSnapshotFromRefList("broken-snapshot-list", nil, makePackageRefList(c), "")
|
||||||
|
c.Assert(collection.Add(snapshot), IsNil)
|
||||||
|
putRawDBValue(c, &s.APISuite, snapshot.RefKey(), []byte("not-msgpack"))
|
||||||
|
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(response.Code, Equals, 500)
|
||||||
|
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package aptly
|
package aptly
|
||||||
|
|
||||||
// Default aptly.conf (filled in at link time)
|
// AptlyConf holds the default aptly.conf (filled in at link time)
|
||||||
var AptlyConf []byte
|
var AptlyConf []byte
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func NewPackagePool(accountName, accountKey, container, prefix, endpoint string)
|
|||||||
return &PackagePool{az: azctx}, nil
|
return &PackagePool{az: azctx}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String
|
// String returns the storage as string
|
||||||
func (pool *PackagePool) String() string {
|
func (pool *PackagePool) String() string {
|
||||||
return pool.az.String()
|
return pool.az.String()
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
|
return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
|
||||||
}
|
}
|
||||||
defer os.Remove(temp.Name())
|
defer func() { _ = os.Remove(temp.Name()) }()
|
||||||
|
|
||||||
_, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
|
_, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -156,7 +156,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer source.Close()
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
err = pool.az.putFile(path, source, checksums.MD5)
|
err = pool.az.putFile(path, source, checksums.MD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+11
-11
@@ -2,12 +2,12 @@ package azure
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
@@ -50,10 +50,10 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
publicAccessType := azblob.PublicAccessTypeContainer
|
publicAccessType := azblob.PublicAccessTypeContainer
|
||||||
_, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
|
_, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
|
||||||
Access: &publicAccessType,
|
Access: &publicAccessType,
|
||||||
})
|
})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||||
@@ -69,8 +69,8 @@ func (s *PackagePoolSuite) TestFilepathList(c *C) {
|
|||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(list, DeepEquals, []string{})
|
c.Check(list, DeepEquals, []string{})
|
||||||
|
|
||||||
s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
_, _ = s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs)
|
_, _ = s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
|
||||||
list, err = s.pool.FilepathList(nil)
|
list, err = s.pool.FilepathList(nil)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
@@ -81,8 +81,8 @@ func (s *PackagePoolSuite) TestFilepathList(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackagePoolSuite) TestRemove(c *C) {
|
func (s *PackagePoolSuite) TestRemove(c *C) {
|
||||||
s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
_, _ = s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
s.pool.Import(s.debFile, "b.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")
|
size, err := s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb")
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
@@ -247,7 +247,7 @@ func (s *PackagePoolSuite) TestOpen(c *C) {
|
|||||||
|
|
||||||
f, err := s.pool.Open(path)
|
f, err := s.pool.Open(path)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
contents, err := ioutil.ReadAll(f)
|
contents, err := io.ReadAll(f)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(len(contents), Equals, 2738)
|
c.Check(len(contents), Equals, 2738)
|
||||||
c.Check(f.Close(), IsNil)
|
c.Check(f.Close(), IsNil)
|
||||||
|
|||||||
+7
-5
@@ -18,7 +18,7 @@ import (
|
|||||||
|
|
||||||
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
||||||
type PublishedStorage struct {
|
type PublishedStorage struct {
|
||||||
prefix string
|
// FIXME: unused ???? prefix string
|
||||||
az *azContext
|
az *azContext
|
||||||
pathCache map[string]map[string]string
|
pathCache map[string]map[string]string
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ func NewPublishedStorage(accountName, accountKey, container, prefix, endpoint st
|
|||||||
return &PublishedStorage{az: azctx}, nil
|
return &PublishedStorage{az: azctx}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String
|
// String returns the storage as string
|
||||||
func (storage *PublishedStorage) String() string {
|
func (storage *PublishedStorage) String() string {
|
||||||
return storage.az.String()
|
return storage.az.String()
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer source.Close()
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
err = storage.az.putFile(path, source, sourceMD5)
|
err = storage.az.putFile(path, source, sourceMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,7 +158,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer source.Close()
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
err = storage.az.putFile(relFilePath, source, sourceMD5)
|
err = storage.az.putFile(relFilePath, source, sourceMD5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -193,7 +193,9 @@ func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error acquiring lease on source blob %s", src)
|
return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||||
}
|
}
|
||||||
defer blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
|
defer func() {
|
||||||
|
_, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
|
||||||
|
}()
|
||||||
|
|
||||||
dstBlobClient := containerClient.NewBlobClient(dst)
|
dstBlobClient := containerClient.NewBlobClient(dst)
|
||||||
copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
|
copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
|
||||||
|
|||||||
+32
-32
@@ -1,17 +1,17 @@
|
|||||||
package azure
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
"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"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
@@ -36,7 +36,7 @@ func randString(n int) string {
|
|||||||
}
|
}
|
||||||
const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
|
const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||||
var bytes = make([]byte, n)
|
var bytes = make([]byte, n)
|
||||||
rand.Read(bytes)
|
_, _ = rand.Read(bytes)
|
||||||
for i, b := range bytes {
|
for i, b := range bytes {
|
||||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
}
|
}
|
||||||
@@ -69,10 +69,10 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
publicAccessType := azblob.PublicAccessTypeContainer
|
publicAccessType := azblob.PublicAccessTypeContainer
|
||||||
_, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
|
_, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
|
||||||
Access: &publicAccessType,
|
Access: &publicAccessType,
|
||||||
})
|
})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||||
@@ -80,39 +80,39 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
||||||
_, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
|
_, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
||||||
resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
|
resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
||||||
serviceClient := s.storage.az.client.ServiceClient()
|
serviceClient := s.storage.az.client.ServiceClient()
|
||||||
containerClient := serviceClient.NewContainerClient(s.storage.az.container)
|
containerClient := serviceClient.NewContainerClient(s.storage.az.container)
|
||||||
blobClient := containerClient.NewBlobClient(path)
|
blobClient := containerClient.NewBlobClient(path)
|
||||||
_, err := blobClient.GetProperties(context.Background(), nil)
|
_, err := blobClient.GetProperties(context.Background(), nil)
|
||||||
c.Assert(err, NotNil)
|
c.Assert(err, NotNil)
|
||||||
|
|
||||||
storageError, ok := err.(*azcore.ResponseError)
|
storageError, ok := err.(*azcore.ResponseError)
|
||||||
c.Assert(ok, Equals, true)
|
c.Assert(ok, Equals, true)
|
||||||
c.Assert(storageError.StatusCode, Equals, 404)
|
c.Assert(storageError.StatusCode, Equals, 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
||||||
hash := md5.Sum(data)
|
hash := md5.Sum(data)
|
||||||
uploadOptions := &azblob.UploadStreamOptions{
|
uploadOptions := &azblob.UploadStreamOptions{
|
||||||
HTTPHeaders: &blob.HTTPHeaders{
|
HTTPHeaders: &blob.HTTPHeaders{
|
||||||
BlobContentMD5: hash[:],
|
BlobContentMD5: hash[:],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
_, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
|
_, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ func (s *PublishedStorageSuite) TestPutFile(c *C) {
|
|||||||
filename := "a/b.txt"
|
filename := "a/b.txt"
|
||||||
|
|
||||||
dir := c.MkDir()
|
dir := c.MkDir()
|
||||||
err := ioutil.WriteFile(filepath.Join(dir, "a"), content, 0644)
|
err := os.WriteFile(filepath.Join(dir, "a"), content, 0644)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
err = s.storage.PutFile(filename, filepath.Join(dir, "a"))
|
err = s.storage.PutFile(filename, filepath.Join(dir, "a"))
|
||||||
@@ -140,7 +140,7 @@ func (s *PublishedStorageSuite) TestPutFilePlus(c *C) {
|
|||||||
filename := "a/b+c.txt"
|
filename := "a/b+c.txt"
|
||||||
|
|
||||||
dir := c.MkDir()
|
dir := c.MkDir()
|
||||||
err := ioutil.WriteFile(filepath.Join(dir, "a"), content, 0644)
|
err := os.WriteFile(filepath.Join(dir, "a"), content, 0644)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
err = s.storage.PutFile(filename, filepath.Join(dir, "a"))
|
err = s.storage.PutFile(filename, filepath.Join(dir, "a"))
|
||||||
@@ -258,7 +258,7 @@ func (s *PublishedStorageSuite) TestRemoveDirsPlus(c *C) {
|
|||||||
|
|
||||||
func (s *PublishedStorageSuite) TestRenameFile(c *C) {
|
func (s *PublishedStorageSuite) TestRenameFile(c *C) {
|
||||||
dir := c.MkDir()
|
dir := c.MkDir()
|
||||||
err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("Welcome to Azure!"), 0644)
|
err := os.WriteFile(filepath.Join(dir, "a"), []byte("Welcome to Azure!"), 0644)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
err = s.storage.PutFile("source.txt", filepath.Join(dir, "a"))
|
err = s.storage.PutFile("source.txt", filepath.Join(dir, "a"))
|
||||||
@@ -280,18 +280,18 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
|
|||||||
cs := files.NewMockChecksumStorage()
|
cs := files.NewMockChecksumStorage()
|
||||||
|
|
||||||
tmpFile1 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
|
tmpFile1 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
|
||||||
err := ioutil.WriteFile(tmpFile1, []byte("Contents"), 0644)
|
err := os.WriteFile(tmpFile1, []byte("Contents"), 0644)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
cksum1 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}
|
cksum1 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}
|
||||||
|
|
||||||
tmpFile2 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
|
tmpFile2 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
|
||||||
err = ioutil.WriteFile(tmpFile2, []byte("Spam"), 0644)
|
err = os.WriteFile(tmpFile2, []byte("Spam"), 0644)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
cksum2 := utils.ChecksumInfo{MD5: "e9dfd31cc505d51fc26975250750deab"}
|
cksum2 := utils.ChecksumInfo{MD5: "e9dfd31cc505d51fc26975250750deab"}
|
||||||
|
|
||||||
tmpFile3 := filepath.Join(c.MkDir(), "netboot/boot.img.gz")
|
tmpFile3 := filepath.Join(c.MkDir(), "netboot/boot.img.gz")
|
||||||
os.MkdirAll(filepath.Dir(tmpFile3), 0777)
|
_ = os.MkdirAll(filepath.Dir(tmpFile3), 0777)
|
||||||
err = ioutil.WriteFile(tmpFile3, []byte("Contents"), 0644)
|
err = os.WriteFile(tmpFile3, []byte("Contents"), 0644)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
cksum3 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}
|
cksum3 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -46,7 +46,7 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
if err == nil && len(listeners) == 1 {
|
if err == nil && len(listeners) == 1 {
|
||||||
listener := listeners[0]
|
listener := listeners[0]
|
||||||
defer listener.Close()
|
defer func() { _ = listener.Close() }()
|
||||||
fmt.Printf("\nTaking over web server at: %s (press Ctrl+C to quit)...\n", listener.Addr().String())
|
fmt.Printf("\nTaking over web server at: %s (press Ctrl+C to quit)...\n", listener.Addr().String())
|
||||||
err = http.Serve(listener, api.Router(context))
|
err = http.Serve(listener, api.Router(context))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -67,7 +67,7 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
|||||||
if _, ok := <-sigchan; ok {
|
if _, ok := <-sigchan; ok {
|
||||||
fmt.Printf("\nShutdown signal received, waiting for background tasks...\n")
|
fmt.Printf("\nShutdown signal received, waiting for background tasks...\n")
|
||||||
context.TaskList().Wait()
|
context.TaskList().Wait()
|
||||||
server.Shutdown(stdcontext.Background())
|
_ = server.Shutdown(stdcontext.Background())
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
defer close(sigchan)
|
defer close(sigchan)
|
||||||
@@ -75,14 +75,14 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
|||||||
listenURL, err := url.Parse(listen)
|
listenURL, err := url.Parse(listen)
|
||||||
if err == nil && listenURL.Scheme == "unix" {
|
if err == nil && listenURL.Scheme == "unix" {
|
||||||
file := listenURL.Path
|
file := listenURL.Path
|
||||||
os.Remove(file)
|
_ = os.Remove(file)
|
||||||
|
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
listener, err = net.Listen("unix", file)
|
listener, err = net.Listen("unix", file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to listen on: %s\n%s", file, err)
|
return fmt.Errorf("failed to listen on: %s\n%s", file, err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer func() { _ = listener.Close() }()
|
||||||
|
|
||||||
err = server.Serve(listener)
|
err = server.Serve(listener)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -97,7 +97,7 @@ package environment to new version.`,
|
|||||||
Flag: *flag.NewFlagSet("aptly", flag.ExitOnError),
|
Flag: *flag.NewFlagSet("aptly", flag.ExitOnError),
|
||||||
Subcommands: []*commander.Command{
|
Subcommands: []*commander.Command{
|
||||||
makeCmdConfig(),
|
makeCmdConfig(),
|
||||||
makeCmdDb(),
|
makeCmdDB(),
|
||||||
makeCmdGraph(),
|
makeCmdGraph(),
|
||||||
makeCmdMirror(),
|
makeCmdMirror(),
|
||||||
makeCmdRepo(),
|
makeCmdRepo(),
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func aptlyConfigShow(_ *commander.Command, _ []string) error {
|
func aptlyConfigShow(_ *commander.Command, _ []string) error {
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import (
|
|||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeCmdDb() *commander.Command {
|
func makeCmdDB() *commander.Command {
|
||||||
return &commander.Command{
|
return &commander.Command{
|
||||||
UsageLine: "db",
|
UsageLine: "db",
|
||||||
Short: "manage aptly's internal database and package pool",
|
Short: "manage aptly's internal database and package pool",
|
||||||
Subcommands: []*commander.Command{
|
Subcommands: []*commander.Command{
|
||||||
makeCmdDbCleanup(),
|
makeCmdDBCleanup(),
|
||||||
makeCmdDbRecover(),
|
makeCmdDBRecover(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-7
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// aptly db cleanup
|
// aptly db cleanup
|
||||||
func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
func aptlyDBCleanup(cmd *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
@@ -26,6 +26,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
// collect information about references packages...
|
// collect information about references packages...
|
||||||
existingPackageRefs := deb.NewPackageRefList()
|
existingPackageRefs := deb.NewPackageRefList()
|
||||||
|
referencedAppStreamFiles := []string{}
|
||||||
|
|
||||||
// used only in verbose mode to report package use source
|
// used only in verbose mode to report package use source
|
||||||
packageRefSources := map[string][]string{}
|
packageRefSources := map[string][]string{}
|
||||||
@@ -48,13 +49,17 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
description := fmt.Sprintf("mirror %s", repo.Name)
|
description := fmt.Sprintf("mirror %s", repo.Name)
|
||||||
repo.RefList().ForEach(func(key []byte) error {
|
_ = repo.RefList().ForEach(func(key []byte) error {
|
||||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, poolPath := range repo.AppStreamFiles {
|
||||||
|
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,7 +86,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
description := fmt.Sprintf("local repo %s", repo.Name)
|
description := fmt.Sprintf("local repo %s", repo.Name)
|
||||||
repo.RefList().ForEach(func(key []byte) error {
|
_ = repo.RefList().ForEach(func(key []byte) error {
|
||||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -113,11 +118,16 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
description := fmt.Sprintf("snapshot %s", snapshot.Name)
|
description := fmt.Sprintf("snapshot %s", snapshot.Name)
|
||||||
snapshot.RefList().ForEach(func(key []byte) error {
|
_ = snapshot.RefList().ForEach(func(key []byte) error {
|
||||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, poolPath := range snapshot.AppStreamFiles {
|
||||||
|
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -146,7 +156,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
if verbose {
|
if verbose {
|
||||||
description := fmt.Sprintf("published repository %s:%s/%s component %s",
|
description := fmt.Sprintf("published repository %s:%s/%s component %s",
|
||||||
published.Storage, published.Prefix, published.Distribution, component)
|
published.Storage, published.Prefix, published.Distribution, component)
|
||||||
published.RefList(component).ForEach(func(key []byte) error {
|
_ = published.RefList(component).ForEach(func(key []byte) error {
|
||||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -236,6 +246,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
referencedFiles = append(referencedFiles, referencedAppStreamFiles...)
|
||||||
sort.Strings(referencedFiles)
|
sort.Strings(referencedFiles)
|
||||||
context.Progress().ShutdownBar()
|
context.Progress().ShutdownBar()
|
||||||
|
|
||||||
@@ -291,9 +302,9 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCmdDbCleanup() *commander.Command {
|
func makeCmdDBCleanup() *commander.Command {
|
||||||
cmd := &commander.Command{
|
cmd := &commander.Command{
|
||||||
Run: aptlyDbCleanup,
|
Run: aptlyDBCleanup,
|
||||||
UsageLine: "cleanup",
|
UsageLine: "cleanup",
|
||||||
Short: "cleanup DB and package pool",
|
Short: "cleanup DB and package pool",
|
||||||
Long: `
|
Long: `
|
||||||
|
|||||||
+45
-4
@@ -1,13 +1,16 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// aptly db recover
|
// aptly db recover
|
||||||
func aptlyDbRecover(cmd *commander.Command, args []string) error {
|
func aptlyDBRecover(cmd *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
@@ -16,14 +19,19 @@ func aptlyDbRecover(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.Progress().Printf("Recovering database...\n")
|
context.Progress().Printf("Recovering database...\n")
|
||||||
err = goleveldb.RecoverDB(context.DBPath())
|
if err = goleveldb.RecoverDB(context.DBPath()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Progress().Printf("Checking database integrity...\n")
|
||||||
|
err = checkIntegrity()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCmdDbRecover() *commander.Command {
|
func makeCmdDBRecover() *commander.Command {
|
||||||
cmd := &commander.Command{
|
cmd := &commander.Command{
|
||||||
Run: aptlyDbRecover,
|
Run: aptlyDBRecover,
|
||||||
UsageLine: "recover",
|
UsageLine: "recover",
|
||||||
Short: "recover DB after crash",
|
Short: "recover DB after crash",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -38,3 +46,36 @@ Example:
|
|||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkIntegrity() error {
|
||||||
|
return context.NewCollectionFactory().LocalRepoCollection().ForEach(checkRepo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRepo(repo *deb.LocalRepo) error {
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
repos := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
err := repos.LoadComplete(repo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load complete repo %q: %s", repo.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dangling, err := deb.FindDanglingReferences(repo.RefList(), collectionFactory.PackageCollection())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("find dangling references: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dangling.Refs) > 0 {
|
||||||
|
for _, ref := range dangling.Refs {
|
||||||
|
context.Progress().Printf("Removing dangling database reference %q\n", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.UpdateRefList(repo.RefList().Subtract(dangling))
|
||||||
|
|
||||||
|
if err = repos.Update(repo); err != nil {
|
||||||
|
return fmt.Errorf("update repo: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -38,8 +38,8 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tempfile.Close()
|
_ = tempfile.Close()
|
||||||
os.Remove(tempfile.Name())
|
_ = os.Remove(tempfile.Name())
|
||||||
|
|
||||||
format := context.Flags().Lookup("format").Value.String()
|
format := context.Flags().Lookup("format").Value.String()
|
||||||
output := context.Flags().Lookup("output").Value.String()
|
output := context.Flags().Lookup("output").Value.String()
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ func getVerifier(flags *flag.FlagSet) (pgp.Verifier, error) {
|
|||||||
verifier.AddKeyring(keyRing)
|
verifier.AddKeyring(keyRing)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := verifier.InitKeyring(ignoreSignatures == false) // be verbose only if verifying signatures is requested
|
err := verifier.InitKeyring(!ignoreSignatures) // be verbose only if verifying signatures is requested
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
|||||||
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
||||||
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
||||||
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
|
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
|
||||||
|
downloadAppStream := context.Flags().Lookup("with-appstream").Value.Get().(bool)
|
||||||
ignoreSignatures := context.Config().GpgDisableVerify
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
if context.Flags().IsSet("ignore-signatures") {
|
if context.Flags().IsSet("ignore-signatures") {
|
||||||
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||||
@@ -41,7 +42,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
|
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
|
||||||
downloadSources, downloadUdebs, downloadInstaller)
|
downloadSources, downloadUdebs, downloadInstaller, downloadAppStream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create mirror: %s", err)
|
return fmt.Errorf("unable to create mirror: %s", err)
|
||||||
}
|
}
|
||||||
@@ -100,6 +101,7 @@ Example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||||
|
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
|
||||||
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
|
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-sources", false, "download source packages in addition to binary packages")
|
||||||
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
repo.Filter = flag.Value.String() // allows file/stdin with @
|
repo.Filter = flag.Value.String() // allows file/stdin with @
|
||||||
case "filter-with-deps":
|
case "filter-with-deps":
|
||||||
repo.FilterWithDeps = flag.Value.Get().(bool)
|
repo.FilterWithDeps = flag.Value.Get().(bool)
|
||||||
|
case "with-appstream":
|
||||||
|
repo.DownloadAppStream = flag.Value.Get().(bool)
|
||||||
case "with-installer":
|
case "with-installer":
|
||||||
repo.DownloadInstaller = flag.Value.Get().(bool)
|
repo.DownloadInstaller = flag.Value.Get().(bool)
|
||||||
case "with-sources":
|
case "with-sources":
|
||||||
@@ -53,6 +55,10 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
|
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if repo.IsFlat() && repo.DownloadAppStream {
|
||||||
|
return fmt.Errorf("unable to edit: flat mirrors don't support AppStream (DEP-11) metadata")
|
||||||
|
}
|
||||||
|
|
||||||
if repo.Filter != "" {
|
if repo.Filter != "" {
|
||||||
_, err = query.Parse(repo.Filter)
|
_, err = query.Parse(repo.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,6 +113,7 @@ Example:
|
|||||||
AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin")
|
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("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("ignore-signatures", false, "disable verification of Release file signatures")
|
||||||
|
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
|
||||||
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
|
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-sources", false, "download source packages in addition to binary packages")
|
||||||
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
||||||
|
|||||||
+4
-4
@@ -32,7 +32,7 @@ func aptlyMirrorListTxt(cmd *commander.Command, _ []string) error {
|
|||||||
|
|
||||||
repos := make([]string, collectionFactory.RemoteRepoCollection().Len())
|
repos := make([]string, collectionFactory.RemoteRepoCollection().Len())
|
||||||
i := 0
|
i := 0
|
||||||
collectionFactory.RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
_ = collectionFactory.RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||||
if raw {
|
if raw {
|
||||||
repos[i] = repo.Name
|
repos[i] = repo.Name
|
||||||
} else {
|
} else {
|
||||||
@@ -42,7 +42,7 @@ func aptlyMirrorListTxt(cmd *commander.Command, _ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
context.CloseDatabase()
|
_ = context.CloseDatabase()
|
||||||
|
|
||||||
sort.Strings(repos)
|
sort.Strings(repos)
|
||||||
|
|
||||||
@@ -70,13 +70,13 @@ func aptlyMirrorListJSON(_ *commander.Command, _ []string) error {
|
|||||||
|
|
||||||
repos := make([]*deb.RemoteRepo, context.NewCollectionFactory().RemoteRepoCollection().Len())
|
repos := make([]*deb.RemoteRepo, context.NewCollectionFactory().RemoteRepoCollection().Len())
|
||||||
i := 0
|
i := 0
|
||||||
context.NewCollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
_ = context.NewCollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||||
repos[i] = repo
|
repos[i] = repo
|
||||||
i++
|
i++
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
context.CloseDatabase()
|
_ = context.CloseDatabase()
|
||||||
|
|
||||||
sort.Slice(repos, func(i, j int) bool {
|
sort.Slice(repos, func(i, j int) bool {
|
||||||
return repos[i].Name < repos[j].Name
|
return repos[i].Name < repos[j].Name
|
||||||
|
|||||||
+7
-2
@@ -61,6 +61,11 @@ func aptlyMirrorShowTxt(_ *commander.Command, args []string) error {
|
|||||||
downloadUdebs = Yes
|
downloadUdebs = Yes
|
||||||
}
|
}
|
||||||
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
|
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
|
||||||
|
downloadAppStream := No
|
||||||
|
if repo.DownloadAppStream {
|
||||||
|
downloadAppStream = Yes
|
||||||
|
}
|
||||||
|
fmt.Printf("Download AppStream: %s\n", downloadAppStream)
|
||||||
if repo.Filter != "" {
|
if repo.Filter != "" {
|
||||||
fmt.Printf("Filter: %s\n", repo.Filter)
|
fmt.Printf("Filter: %s\n", repo.Filter)
|
||||||
filterWithDeps := No
|
filterWithDeps := No
|
||||||
@@ -86,7 +91,7 @@ func aptlyMirrorShowTxt(_ *commander.Command, args []string) error {
|
|||||||
if repo.LastDownloadDate.IsZero() {
|
if repo.LastDownloadDate.IsZero() {
|
||||||
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
|
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
|
||||||
} else {
|
} else {
|
||||||
ListPackagesRefList(repo.RefList(), collectionFactory)
|
_ = ListPackagesRefList(repo.RefList(), collectionFactory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +124,7 @@ func aptlyMirrorShowJSON(_ *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list.PrepareIndex()
|
list.PrepareIndex()
|
||||||
list.ForEachIndexed(func(p *deb.Package) error {
|
_ = list.ForEachIndexed(func(p *deb.Package) error {
|
||||||
repo.Packages = append(repo.Packages, p.GetFullName())
|
repo.Packages = append(repo.Packages, p.GetFullName())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
+15
-4
@@ -64,6 +64,15 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if repo.DownloadAppStream && !repo.IsFlat() {
|
||||||
|
context.Progress().Printf("Downloading AppStream metadata...\n")
|
||||||
|
err = repo.DownloadAppStreamFiles(context.Progress(), context.Downloader(),
|
||||||
|
context.PackagePool(), collectionFactory.ChecksumCollection(nil), ignoreChecksums)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if repo.Filter != "" {
|
if repo.Filter != "" {
|
||||||
context.Progress().Printf("Applying filter...\n")
|
context.Progress().Printf("Applying filter...\n")
|
||||||
var filterQuery deb.PackageQuery
|
var filterQuery deb.PackageQuery
|
||||||
@@ -87,10 +96,11 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
|
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
|
||||||
|
latestOnly := context.Flags().Lookup("latest").Value.Get().(bool)
|
||||||
|
|
||||||
context.Progress().Printf("Building download queue...\n")
|
context.Progress().Printf("Building download queue...\n")
|
||||||
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
||||||
collectionFactory.ChecksumCollection(nil), skipExistingPackages)
|
collectionFactory.ChecksumCollection(nil), skipExistingPackages, latestOnly)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
@@ -101,7 +111,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
err = context.ReOpenDatabase()
|
err = context.ReOpenDatabase()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
repo.MarkAsIdle()
|
repo.MarkAsIdle()
|
||||||
collectionFactory.RemoteRepoCollection().Update(repo)
|
_ = collectionFactory.RemoteRepoCollection().Update(repo)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -173,7 +183,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
file, e = os.CreateTemp("", task.File.Filename)
|
file, e = os.CreateTemp("", task.File.Filename)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
task.TempDownPath = file.Name()
|
task.TempDownPath = file.Name()
|
||||||
file.Close()
|
_ = file.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@@ -261,7 +271,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n "))
|
return fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n "))
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.FinalizeDownload(collectionFactory, context.Progress())
|
_ = repo.FinalizeDownload(collectionFactory, context.Progress())
|
||||||
err = collectionFactory.RemoteRepoCollection().Update(repo)
|
err = collectionFactory.RemoteRepoCollection().Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
@@ -292,6 +302,7 @@ Example:
|
|||||||
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
||||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||||
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
|
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
|
||||||
|
cmd.Flag.Bool("latest", false, "download only latest version of each package (per architecture)")
|
||||||
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
||||||
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
|
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
|
||||||
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
|
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func aptlyPackageSearch(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
format := context.Flags().Lookup("format").Value.String()
|
format := context.Flags().Lookup("format").Value.String()
|
||||||
PrintPackageList(result, format, "")
|
_ = PrintPackageList(result, format, "")
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -84,8 +84,8 @@ func aptlyPackageShow(cmd *commander.Command, args []string) error {
|
|||||||
result := q.Query(collectionFactory.PackageCollection())
|
result := q.Query(collectionFactory.PackageCollection())
|
||||||
|
|
||||||
err = result.ForEach(func(p *deb.Package) error {
|
err = result.ForEach(func(p *deb.Package) error {
|
||||||
p.Stanza().WriteTo(w, p.IsSource, false, false)
|
_ = p.Stanza().WriteTo(w, p.IsSource, false, false)
|
||||||
w.Flush()
|
_ = w.Flush()
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
|
|
||||||
if withFiles {
|
if withFiles {
|
||||||
@@ -109,7 +109,7 @@ func aptlyPackageShow(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
if withReferences {
|
if withReferences {
|
||||||
fmt.Printf("References to package:\n")
|
fmt.Printf("References to package:\n")
|
||||||
printReferencesTo(p, collectionFactory)
|
_ = printReferencesTo(p, collectionFactory)
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+33
-1
@@ -1,6 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/pgp"
|
"github.com/aptly-dev/aptly/pgp"
|
||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
"github.com/smira/flag"
|
"github.com/smira/flag"
|
||||||
@@ -12,7 +14,20 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signer := context.GetSigner()
|
signer := context.GetSigner()
|
||||||
signer.SetKey(flags.Lookup("gpg-key").Value.String())
|
|
||||||
|
var gpgKeys []string
|
||||||
|
|
||||||
|
// CLI args have priority over config
|
||||||
|
cliKeys := flags.Lookup("gpg-key").Value.Get().([]string)
|
||||||
|
if len(cliKeys) > 0 {
|
||||||
|
gpgKeys = cliKeys
|
||||||
|
} else if len(context.Config().GpgKeys) > 0 {
|
||||||
|
gpgKeys = context.Config().GpgKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, gpgKey := range gpgKeys {
|
||||||
|
signer.SetKey(gpgKey)
|
||||||
|
}
|
||||||
signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String())
|
signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String())
|
||||||
signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
|
signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
|
||||||
signer.SetBatch(flags.Lookup("batch").Value.Get().(bool))
|
signer.SetBatch(flags.Lookup("batch").Value.Get().(bool))
|
||||||
@@ -26,6 +41,23 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gpgKeyFlag struct {
|
||||||
|
gpgKeys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *gpgKeyFlag) Set(value string) error {
|
||||||
|
k.gpgKeys = append(k.gpgKeys, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *gpgKeyFlag) Get() interface{} {
|
||||||
|
return k.gpgKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *gpgKeyFlag) String() string {
|
||||||
|
return strings.Join(k.gpgKeys, ",")
|
||||||
|
}
|
||||||
|
|
||||||
func makeCmdPublish() *commander.Command {
|
func makeCmdPublish() *commander.Command {
|
||||||
return &commander.Command{
|
return &commander.Command{
|
||||||
UsageLine: "publish",
|
UsageLine: "publish",
|
||||||
|
|||||||
+2
-2
@@ -53,7 +53,7 @@ func aptlyPublishListTxt(cmd *commander.Command, _ []string) error {
|
|||||||
return fmt.Errorf("unable to load list of repos: %s", err)
|
return fmt.Errorf("unable to load list of repos: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.CloseDatabase()
|
_ = context.CloseDatabase()
|
||||||
|
|
||||||
sort.Strings(published)
|
sort.Strings(published)
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ func aptlyPublishListJSON(_ *commander.Command, _ []string) error {
|
|||||||
return fmt.Errorf("unable to load list of repos: %s", err)
|
return fmt.Errorf("unable to load list of repos: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.CloseDatabase()
|
_ = context.CloseDatabase()
|
||||||
|
|
||||||
sort.Slice(repos, func(i, j int) bool {
|
sort.Slice(repos, func(i, j int) bool {
|
||||||
return repos[i].GetPath() < repos[j].GetPath()
|
return repos[i].GetPath() < repos[j].GetPath()
|
||||||
|
|||||||
+3
-1
@@ -34,7 +34,7 @@ Example:
|
|||||||
}
|
}
|
||||||
cmd.Flag.String("distribution", "", "distribution name to publish")
|
cmd.Flag.String("distribution", "", "distribution name to publish")
|
||||||
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
||||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
|
||||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||||
@@ -51,7 +51,9 @@ Example:
|
|||||||
cmd.Flag.String("codename", "", "codename 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("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("acquire-by-hash", false, "provide index files by hash")
|
||||||
|
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
|
||||||
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
cmd.Flag.String("version", "", "version of the release")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -150,13 +150,21 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
|||||||
published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool)
|
published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("signed-by") {
|
||||||
|
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
if context.Flags().IsSet("multi-dist") {
|
if context.Flags().IsSet("multi-dist") {
|
||||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("version") {
|
||||||
|
published.Version = context.Flags().Lookup("version").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
|
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
|
||||||
if duplicate != nil {
|
if duplicate != nil {
|
||||||
collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
||||||
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +238,7 @@ Example:
|
|||||||
}
|
}
|
||||||
cmd.Flag.String("distribution", "", "distribution name to publish")
|
cmd.Flag.String("distribution", "", "distribution name to publish")
|
||||||
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
||||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
|
||||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||||
@@ -247,6 +255,8 @@ Example:
|
|||||||
cmd.Flag.String("codename", "", "codename 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("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("acquire-by-hash", false, "provide index files by hash")
|
||||||
|
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
|
||||||
|
cmd.Flag.String("version", "", "version of the release")
|
||||||
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
+11
-1
@@ -99,6 +99,14 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("signed-by") {
|
||||||
|
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("version") {
|
||||||
|
published.Version = context.Flags().Lookup("version").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
if context.Flags().IsSet("multi-dist") {
|
if context.Flags().IsSet("multi-dist") {
|
||||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
@@ -151,7 +159,7 @@ This command would switch published repository (with one component) named ppa/wh
|
|||||||
`,
|
`,
|
||||||
Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
|
Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
|
||||||
}
|
}
|
||||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
|
||||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||||
@@ -162,6 +170,8 @@ This command would switch published repository (with one component) named ppa/wh
|
|||||||
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
||||||
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
|
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("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
|
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
|
||||||
|
cmd.Flag.String("version", "", "version of the release")
|
||||||
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
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")
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
|
|||||||
+21
-1
@@ -60,10 +60,26 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("signed-by") {
|
||||||
|
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("origin") {
|
||||||
|
published.Origin = context.Flags().Lookup("origin").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("label") {
|
||||||
|
published.Label = context.Flags().Lookup("label").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
if context.Flags().IsSet("multi-dist") {
|
if context.Flags().IsSet("multi-dist") {
|
||||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("version") {
|
||||||
|
published.Version = context.Flags().Lookup("version").Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to publish: %s", err)
|
return fmt.Errorf("unable to publish: %s", err)
|
||||||
@@ -115,7 +131,7 @@ Example:
|
|||||||
`,
|
`,
|
||||||
Flag: *flag.NewFlagSet("aptly-publish-update", flag.ExitOnError),
|
Flag: *flag.NewFlagSet("aptly-publish-update", flag.ExitOnError),
|
||||||
}
|
}
|
||||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
|
||||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||||
@@ -125,8 +141,12 @@ Example:
|
|||||||
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
|
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
|
||||||
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
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("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
|
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
|
||||||
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
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")
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
cmd.Flag.String("origin", "", "overwrite origin name to publish")
|
||||||
|
cmd.Flag.String("label", "", "overwrite label to publish")
|
||||||
|
cmd.Flag.String("version", "", "version of the release")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -32,7 +32,7 @@ func aptlyRepoListTxt(cmd *commander.Command, _ []string) error {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
repos := make([]string, collectionFactory.LocalRepoCollection().Len())
|
repos := make([]string, collectionFactory.LocalRepoCollection().Len())
|
||||||
i := 0
|
i := 0
|
||||||
collectionFactory.LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
_ = collectionFactory.LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||||
if raw {
|
if raw {
|
||||||
repos[i] = repo.Name
|
repos[i] = repo.Name
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +47,7 @@ func aptlyRepoListTxt(cmd *commander.Command, _ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
context.CloseDatabase()
|
_ = context.CloseDatabase()
|
||||||
|
|
||||||
sort.Strings(repos)
|
sort.Strings(repos)
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ func aptlyRepoListJSON(_ *commander.Command, _ []string) error {
|
|||||||
|
|
||||||
repos := make([]*deb.LocalRepo, context.NewCollectionFactory().LocalRepoCollection().Len())
|
repos := make([]*deb.LocalRepo, context.NewCollectionFactory().LocalRepoCollection().Len())
|
||||||
i := 0
|
i := 0
|
||||||
context.NewCollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
_ = context.NewCollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||||
e := context.NewCollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
e := context.NewCollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
@@ -87,7 +87,7 @@ func aptlyRepoListJSON(_ *commander.Command, _ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
context.CloseDatabase()
|
_ = context.CloseDatabase()
|
||||||
|
|
||||||
sort.Slice(repos, func(i, j int) bool {
|
sort.Slice(repos, func(i, j int) bool {
|
||||||
return repos[i].Name < repos[j].Name
|
return repos[i].Name < repos[j].Name
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to remove: %s", err)
|
return fmt.Errorf("unable to remove: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
toRemove.ForEach(func(p *deb.Package) error {
|
_ = toRemove.ForEach(func(p *deb.Package) error {
|
||||||
list.Remove(p)
|
list.Remove(p)
|
||||||
context.Progress().ColoredPrintf("@r[-]@| %s removed", p)
|
context.Progress().ColoredPrintf("@r[-]@| %s removed", p)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ func aptlyRepoShowTxt(_ *commander.Command, args []string) error {
|
|||||||
|
|
||||||
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
|
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
|
||||||
if withPackages {
|
if withPackages {
|
||||||
ListPackagesRefList(repo.RefList(), collectionFactory)
|
_ = ListPackagesRefList(repo.RefList(), collectionFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func aptlySnapshotListTxt(cmd *commander.Command, _ []string) error {
|
|||||||
collection := collectionFactory.SnapshotCollection()
|
collection := collectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
if raw {
|
if raw {
|
||||||
collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
|
_ = collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
|
||||||
fmt.Printf("%s\n", snapshot.Name)
|
fmt.Printf("%s\n", snapshot.Name)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -68,7 +68,7 @@ func aptlySnapshotListJSON(cmd *commander.Command, _ []string) error {
|
|||||||
|
|
||||||
jsonSnapshots := make([]*deb.Snapshot, collection.Len())
|
jsonSnapshots := make([]*deb.Snapshot, collection.Len())
|
||||||
i := 0
|
i := 0
|
||||||
collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
|
_ = collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
|
||||||
jsonSnapshots[i] = snapshot
|
jsonSnapshots[i] = snapshot
|
||||||
i++
|
i++
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
alreadySeen := map[string]bool{}
|
alreadySeen := map[string]bool{}
|
||||||
|
|
||||||
result.ForEachIndexed(func(pkg *deb.Package) error {
|
_ = result.ForEachIndexed(func(pkg *deb.Package) error {
|
||||||
key := pkg.Architecture + "_" + pkg.Name
|
key := pkg.Architecture + "_" + pkg.Name
|
||||||
_, seen := alreadySeen[key]
|
_, seen := alreadySeen[key]
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
// If !allMatches, add only first matching name-arch package
|
// If !allMatches, add only first matching name-arch package
|
||||||
if !seen || allMatches {
|
if !seen || allMatches {
|
||||||
packageList.Add(pkg)
|
_ = packageList.Add(pkg)
|
||||||
context.Progress().ColoredPrintf("@g[+]@| %s added", pkg)
|
context.Progress().ColoredPrintf("@g[+]@| %s added", pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
format := context.Flags().Lookup("format").Value.String()
|
format := context.Flags().Lookup("format").Value.String()
|
||||||
PrintPackageList(result, format, "")
|
_ = PrintPackageList(result, format, "")
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func aptlySnapshotShowTxt(_ *commander.Command, args []string) error {
|
|||||||
|
|
||||||
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
|
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
|
||||||
if withPackages {
|
if withPackages {
|
||||||
ListPackagesRefList(snapshot.RefList(), collectionFactory)
|
_ = ListPackagesRefList(snapshot.RefList(), collectionFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -139,7 +139,7 @@ func aptlySnapshotShowJSON(_ *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list.PrepareIndex()
|
list.PrepareIndex()
|
||||||
list.ForEachIndexed(func(p *deb.Package) error {
|
_ = list.ForEachIndexed(func(p *deb.Package) error {
|
||||||
snapshot.Packages = append(snapshot.Packages, p.GetFullName())
|
snapshot.Packages = append(snapshot.Packages, p.GetFullName())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattn/go-shellwords"
|
shellwords "github.com/mattn/go-shellwords"
|
||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
$keyring \
|
$keyring \
|
||||||
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
||||||
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
||||||
|
"-with-appstream=[download AppStream (DEP-11) metadata]:$bool" \
|
||||||
"(-)2:new mirror name: " ":archive url:_urls" ":distribution:($dists)" "*:components:_values -s ' ' components $components"
|
"(-)2:new mirror name: " ":archive url:_urls" ":distribution:($dists)" "*:components:_values -s ' ' components $components"
|
||||||
;;
|
;;
|
||||||
list)
|
list)
|
||||||
@@ -211,6 +212,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
$keyring \
|
$keyring \
|
||||||
"-max-tries=[max download tries till process fails with download error]:number: " \
|
"-max-tries=[max download tries till process fails with download error]:number: " \
|
||||||
"-skip-existing-packages=[do not check file existence for packages listed in the internal database of the mirror]:$bool" \
|
"-skip-existing-packages=[do not check file existence for packages listed in the internal database of the mirror]:$bool" \
|
||||||
|
"-latest=[download only latest version of each package (per architecture)]:$bool" \
|
||||||
"(-)2:mirror name:$mirrors"
|
"(-)2:mirror name:$mirrors"
|
||||||
;;
|
;;
|
||||||
rename)
|
rename)
|
||||||
@@ -223,6 +225,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
"-filter-with-deps=[when filtering, include dependencies of matching packages as well]:$bool" \
|
"-filter-with-deps=[when filtering, include dependencies of matching packages as well]:$bool" \
|
||||||
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
||||||
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
||||||
|
"-with-appstream=[download AppStream (DEP-11) metadata]:$bool" \
|
||||||
"(-)2:mirror name:$mirrors"
|
"(-)2:mirror name:$mirrors"
|
||||||
;;
|
;;
|
||||||
search)
|
search)
|
||||||
|
|||||||
+48
-12
@@ -22,34 +22,36 @@
|
|||||||
|
|
||||||
__aptly_mirror_list()
|
__aptly_mirror_list()
|
||||||
{
|
{
|
||||||
aptly mirror list -raw
|
aptly ${aptly_global_opts[@]} mirror list -raw
|
||||||
}
|
}
|
||||||
|
|
||||||
__aptly_repo_list()
|
__aptly_repo_list()
|
||||||
{
|
{
|
||||||
aptly repo list -raw
|
aptly ${aptly_global_opts[@]} repo list -raw
|
||||||
}
|
}
|
||||||
|
|
||||||
__aptly_snapshot_list()
|
__aptly_snapshot_list()
|
||||||
{
|
{
|
||||||
aptly snapshot list -raw
|
aptly ${aptly_global_opts[@]} snapshot list -raw
|
||||||
}
|
}
|
||||||
|
|
||||||
__aptly_published_distributions()
|
__aptly_published_distributions()
|
||||||
{
|
{
|
||||||
aptly publish list -raw | cut -d ' ' -f 2 | sort | uniq
|
aptly ${aptly_global_opts[@]} publish list -raw | cut -d ' ' -f 2 | sort | uniq
|
||||||
}
|
}
|
||||||
|
|
||||||
__aptly_published_prefixes()
|
__aptly_published_prefixes()
|
||||||
{
|
{
|
||||||
aptly publish list -raw | cut -d ' ' -f 1 | sort | uniq
|
aptly ${aptly_global_opts[@]} publish list -raw | cut -d ' ' -f 1 | sort | uniq
|
||||||
}
|
}
|
||||||
|
|
||||||
__aptly_prefixes_for_distribution()
|
__aptly_prefixes_for_distribution()
|
||||||
{
|
{
|
||||||
aptly publish list -raw | awk -v dist="$1" '{ if (dist == $2) print $1 }' | sort | uniq
|
aptly ${aptly_global_opts[@]} publish list -raw | awk -v dist="$1" '{ if (dist == $2) print $1 }' | sort | uniq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_aptly()
|
_aptly()
|
||||||
{
|
{
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
@@ -57,7 +59,12 @@ _aptly()
|
|||||||
prevprev="${COMP_WORDS[COMP_CWORD-2]}"
|
prevprev="${COMP_WORDS[COMP_CWORD-2]}"
|
||||||
|
|
||||||
commands="api config db graph mirror package publish repo serve snapshot task version"
|
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="
|
|
||||||
|
options="-architectures -config -db-open-attempts -dep-follow-all-variants -dep-follow-recommends -dep-follow-source -dep-follow-suggests -dep-verbose-resolve -gpg-provider"
|
||||||
|
options_without_arg="-dep-follow-all-variants -dep-follow-recommends -dep-follow-source -dep-follow-suggests -dep-verbose-resolve"
|
||||||
|
options_with_arg="-architectures -db-open-attempts -gpg-provider"
|
||||||
|
options_with_path_arg="-config"
|
||||||
|
|
||||||
db_subcommands="cleanup recover"
|
db_subcommands="cleanup recover"
|
||||||
mirror_subcommands="create drop edit show list rename search update"
|
mirror_subcommands="create drop edit show list rename search update"
|
||||||
publish_subcommands="drop list repo snapshot switch update source"
|
publish_subcommands="drop list repo snapshot switch update source"
|
||||||
@@ -69,12 +76,41 @@ _aptly()
|
|||||||
config_subcommands="show"
|
config_subcommands="show"
|
||||||
api_subcommands="serve"
|
api_subcommands="serve"
|
||||||
|
|
||||||
local cmd subcmd numargs numoptions i
|
local cmd subcmd numargs numoptions i aptly_global_opts
|
||||||
|
|
||||||
numargs=0
|
numargs=0
|
||||||
numoptions=0
|
numoptions=0
|
||||||
|
|
||||||
|
for opt in "${options_with_path_arg[@]}"; do
|
||||||
|
[[ "$prev" == "$opt" ]] || continue
|
||||||
|
compopt -o filenames 2>/dev/null
|
||||||
|
_filedir
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
|
||||||
for (( i=1; i < $COMP_CWORD; i++ )); do
|
for (( i=1; i < $COMP_CWORD; i++ )); do
|
||||||
|
word=${COMP_WORDS[i]}
|
||||||
|
if [[ "$word" == -*=* ]]; then
|
||||||
|
for o in "${options[@]}"; do
|
||||||
|
[[ ${word%%=*} == "$o" ]] && aptly_global_opts+=("$word")
|
||||||
|
done
|
||||||
|
else
|
||||||
|
for o in "${options_with_arg[@]}" ""${options_with_path_arg[@]}"" ; do
|
||||||
|
if [[ "$word" == "$o" ]]; then
|
||||||
|
if (( i + 1 < COMP_CWORD )); then
|
||||||
|
aptly_global_opts+=("$word" "${COMP_WORDS[i+1]}")
|
||||||
|
else
|
||||||
|
aptly_global_opts+=("$word")
|
||||||
|
fi
|
||||||
|
(( i++ ))
|
||||||
|
continue 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
for o in ${options_without_arg[@]}; do
|
||||||
|
[[ "$word" == "$o" ]] && aptly_global_opts+=("$word")
|
||||||
|
done
|
||||||
|
|
||||||
if [[ -n "$cmd" ]]; then
|
if [[ -n "$cmd" ]]; then
|
||||||
if [[ ! -n "$subcmd" ]]; then
|
if [[ ! -n "$subcmd" ]]; then
|
||||||
subcmd=${COMP_WORDS[i]}
|
subcmd=${COMP_WORDS[i]}
|
||||||
@@ -167,7 +203,7 @@ _aptly()
|
|||||||
"create")
|
"create")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-installer -with-sources -with-udebs" -- ${cur}))
|
COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-appstream -with-installer -with-sources -with-udebs" -- ${cur}))
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -175,7 +211,7 @@ _aptly()
|
|||||||
"edit")
|
"edit")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-archive-url= -filter= -filter-with-deps -ignore-signatures -keyring= -with-installer -with-sources -with-udebs" -- ${cur}))
|
COMPREPLY=($(compgen -W "-archive-url= -filter= -filter-with-deps -ignore-signatures -keyring= -with-appstream -with-installer -with-sources -with-udebs" -- ${cur}))
|
||||||
else
|
else
|
||||||
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
||||||
fi
|
fi
|
||||||
@@ -227,7 +263,7 @@ _aptly()
|
|||||||
"update")
|
"update")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages" -- ${cur}))
|
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages -latest" -- ${cur}))
|
||||||
else
|
else
|
||||||
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
||||||
fi
|
fi
|
||||||
@@ -339,7 +375,7 @@ _aptly()
|
|||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-accept-unsigned -force-replace -ignore-signatures -keyring= -no-remove-files -repo= -uploaders-file=" -- ${cur}))
|
COMPREPLY=($(compgen -W "-accept-unsigned -force-replace -ignore-signatures -keyring= -no-remove-files -repo= -uploaders-file=" -- ${cur}))
|
||||||
else
|
else
|
||||||
comptopt -o filenames 2>/dev/null
|
compopt -o filenames 2>/dev/null
|
||||||
COMPREPLY=($(compgen -f -- ${cur}))
|
COMPREPLY=($(compgen -f -- ${cur}))
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
+3
-2
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/cheggaaa/pb"
|
"github.com/cheggaaa/pb"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/wsxiaoys/terminal/color"
|
"github.com/wsxiaoys/terminal/color"
|
||||||
@@ -78,7 +79,7 @@ func (p *Progress) InitBar(count int64, isBytes bool, _ aptly.BarType) {
|
|||||||
if p.bar != nil {
|
if p.bar != nil {
|
||||||
panic("bar already initialized")
|
panic("bar already initialized")
|
||||||
}
|
}
|
||||||
if RunningOnTerminal() {
|
if utils.RunningOnTerminal() {
|
||||||
p.bar = pb.New(0)
|
p.bar = pb.New(0)
|
||||||
p.bar.Total = count
|
p.bar.Total = count
|
||||||
p.bar.NotPrint = true
|
p.bar.NotPrint = true
|
||||||
@@ -141,7 +142,7 @@ func (p *Progress) PrintfStdErr(msg string, a ...interface{}) {
|
|||||||
|
|
||||||
// ColoredPrintf does printf in colored way + newline
|
// ColoredPrintf does printf in colored way + newline
|
||||||
func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
|
func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
|
||||||
if RunningOnTerminal() {
|
if utils.RunningOnTerminal() {
|
||||||
p.queue <- printTask{code: codePrint, message: color.Sprintf(msg, a...) + "\n"}
|
p.queue <- printTask{code: codePrint, message: color.Sprintf(msg, a...) + "\n"}
|
||||||
} else {
|
} else {
|
||||||
// stip color marks
|
// stip color marks
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func Test(t *testing.T) {
|
|||||||
TestingT(t)
|
TestingT(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProgressSuite struct {}
|
type ProgressSuite struct{}
|
||||||
|
|
||||||
var _ = Suite(&ProgressSuite{})
|
var _ = Suite(&ProgressSuite{})
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package console
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RunningOnTerminal checks whether stdout is terminal
|
|
||||||
func RunningOnTerminal() bool {
|
|
||||||
return term.IsTerminal(syscall.Stdout)
|
|
||||||
}
|
|
||||||
+21
-13
@@ -115,7 +115,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", homeLocation)
|
fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", homeLocation)
|
||||||
|
|
||||||
utils.SaveConfigRaw(homeLocation, aptly.AptlyConf)
|
_ = utils.SaveConfigRaw(homeLocation, aptly.AptlyConf)
|
||||||
err = utils.LoadConfig(homeLocation, &utils.Config)
|
err = utils.LoadConfig(homeLocation, &utils.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatal(fmt.Errorf("error loading config file %s: %s", homeLocation, err))
|
Fatal(fmt.Errorf("error loading config file %s: %s", homeLocation, err))
|
||||||
@@ -123,6 +123,14 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if utils.Config.LogFormat == "json" {
|
||||||
|
context.StructuredLogging(true)
|
||||||
|
utils.SetupJSONLogger(utils.Config.LogLevel, os.Stdout)
|
||||||
|
} else {
|
||||||
|
context.StructuredLogging(false)
|
||||||
|
utils.SetupDefaultLogger(utils.Config.LogLevel)
|
||||||
|
}
|
||||||
|
|
||||||
context.configLoaded = true
|
context.configLoaded = true
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -233,7 +241,7 @@ func (context *AptlyContext) newDownloader(progress aptly.Progress) aptly.Downlo
|
|||||||
// If flag is defined prefer it to global setting
|
// If flag is defined prefer it to global setting
|
||||||
maxTries = maxTriesFlag.Value.Get().(int)
|
maxTries = maxTriesFlag.Value.Get().(int)
|
||||||
}
|
}
|
||||||
var downloader string = context.config().Downloader
|
var downloader = context.config().Downloader
|
||||||
downloaderFlag := context.flags.Lookup("downloader")
|
downloaderFlag := context.flags.Lookup("downloader")
|
||||||
if downloaderFlag != nil {
|
if downloaderFlag != nil {
|
||||||
downloader = downloaderFlag.Value.String()
|
downloader = downloaderFlag.Value.String()
|
||||||
@@ -295,8 +303,8 @@ func (context *AptlyContext) _database() (database.Storage, error) {
|
|||||||
switch context.config().DatabaseBackend.Type {
|
switch context.config().DatabaseBackend.Type {
|
||||||
case "leveldb":
|
case "leveldb":
|
||||||
dbPath := filepath.Join(context.config().GetRootDir(), "db")
|
dbPath := filepath.Join(context.config().GetRootDir(), "db")
|
||||||
if len(context.config().DatabaseBackend.DbPath) != 0 {
|
if len(context.config().DatabaseBackend.DBPath) != 0 {
|
||||||
dbPath = context.config().DatabaseBackend.DbPath
|
dbPath = context.config().DatabaseBackend.DBPath
|
||||||
}
|
}
|
||||||
context.database, err = goleveldb.NewDB(dbPath)
|
context.database, err = goleveldb.NewDB(dbPath)
|
||||||
case "etcd":
|
case "etcd":
|
||||||
@@ -444,7 +452,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto
|
|||||||
} else if strings.HasPrefix(name, "azure:") {
|
} else if strings.HasPrefix(name, "azure:") {
|
||||||
params, ok := context.config().AzurePublishRoots[name[6:]]
|
params, ok := context.config().AzurePublishRoots[name[6:]]
|
||||||
if !ok {
|
if !ok {
|
||||||
Fatal(fmt.Errorf("Published Azure storage %v not configured", name[6:]))
|
Fatal(fmt.Errorf("published Azure storage %v not configured", name[6:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -589,17 +597,17 @@ func (context *AptlyContext) Shutdown() {
|
|||||||
|
|
||||||
if aptly.EnableDebug {
|
if aptly.EnableDebug {
|
||||||
if context.fileMemProfile != nil {
|
if context.fileMemProfile != nil {
|
||||||
pprof.WriteHeapProfile(context.fileMemProfile)
|
_ = pprof.WriteHeapProfile(context.fileMemProfile)
|
||||||
context.fileMemProfile.Close()
|
_ = context.fileMemProfile.Close()
|
||||||
context.fileMemProfile = nil
|
context.fileMemProfile = nil
|
||||||
}
|
}
|
||||||
if context.fileCPUProfile != nil {
|
if context.fileCPUProfile != nil {
|
||||||
pprof.StopCPUProfile()
|
pprof.StopCPUProfile()
|
||||||
context.fileCPUProfile.Close()
|
_ = context.fileCPUProfile.Close()
|
||||||
context.fileCPUProfile = nil
|
context.fileCPUProfile = nil
|
||||||
}
|
}
|
||||||
if context.fileMemProfile != nil {
|
if context.fileMemProfile != nil {
|
||||||
context.fileMemProfile.Close()
|
_ = context.fileMemProfile.Close()
|
||||||
context.fileMemProfile = nil
|
context.fileMemProfile = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,7 +615,7 @@ func (context *AptlyContext) Shutdown() {
|
|||||||
context.taskList.Stop()
|
context.taskList.Stop()
|
||||||
}
|
}
|
||||||
if context.database != nil {
|
if context.database != nil {
|
||||||
context.database.Close()
|
_ = context.database.Close()
|
||||||
context.database = nil
|
context.database = nil
|
||||||
}
|
}
|
||||||
if context.downloader != nil {
|
if context.downloader != nil {
|
||||||
@@ -652,7 +660,7 @@ func NewContext(flags *flag.FlagSet) (*AptlyContext, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pprof.StartCPUProfile(context.fileCPUProfile)
|
_ = pprof.StartCPUProfile(context.fileCPUProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
memprofile := flags.Lookup("memprofile").Value.String()
|
memprofile := flags.Lookup("memprofile").Value.String()
|
||||||
@@ -672,7 +680,7 @@ func NewContext(flags *flag.FlagSet) (*AptlyContext, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
|
_, _ = context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var stats runtime.MemStats
|
var stats runtime.MemStats
|
||||||
@@ -682,7 +690,7 @@ func NewContext(flags *flag.FlagSet) (*AptlyContext, error) {
|
|||||||
for {
|
for {
|
||||||
runtime.ReadMemStats(&stats)
|
runtime.ReadMemStats(&stats)
|
||||||
if context.fileMemStats != nil {
|
if context.fileMemStats != nil {
|
||||||
context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
|
_, _ = context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
|
||||||
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
|
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
|
||||||
time.Sleep(interval)
|
time.Sleep(interval)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ func Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EtcDDBSuite struct {
|
type EtcDDBSuite struct {
|
||||||
url string
|
db database.Storage
|
||||||
db database.Storage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Suite(&EtcDDBSuite{})
|
var _ = Suite(&EtcDDBSuite{})
|
||||||
@@ -67,17 +66,17 @@ func (s *EtcDDBSuite) TestDelete(c *C) {
|
|||||||
func (s *EtcDDBSuite) TestByPrefix(c *C) {
|
func (s *EtcDDBSuite) TestByPrefix(c *C) {
|
||||||
//c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
//c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
||||||
|
|
||||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
_ = s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
_ = s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||||
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
_ = s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
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}})
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
_ = s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
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}})
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
_ = s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
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}})
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
@@ -109,7 +108,7 @@ func (s *EtcDDBSuite) TestHasPrefix(c *C) {
|
|||||||
//c.Check(s.db.HasPrefix([]byte(nil)), Equals, false)
|
//c.Check(s.db.HasPrefix([]byte(nil)), Equals, false)
|
||||||
//c.Check(s.db.HasPrefix([]byte{0x80}), Equals, false)
|
//c.Check(s.db.HasPrefix([]byte{0x80}), Equals, false)
|
||||||
|
|
||||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
_ = s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
|
|
||||||
c.Check(s.db.HasPrefix([]byte(nil)), Equals, true)
|
c.Check(s.db.HasPrefix([]byte(nil)), Equals, true)
|
||||||
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true)
|
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true)
|
||||||
@@ -124,15 +123,17 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
|
|||||||
value2 = []byte("value2")
|
value2 = []byte("value2")
|
||||||
)
|
)
|
||||||
transaction, err := s.db.OpenTransaction()
|
transaction, err := s.db.OpenTransaction()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
err = s.db.Put(key, value)
|
err = s.db.Put(key, value)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
transaction.Put(key2, value2)
|
_ = transaction.Put(key2, value2)
|
||||||
v, err := s.db.Get(key)
|
v, err := s.db.Get(key)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
c.Check(v, DeepEquals, value)
|
c.Check(v, DeepEquals, value)
|
||||||
err = transaction.Delete(key)
|
err = transaction.Delete(key)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
_, err = transaction.Get(key2)
|
_, err = transaction.Get(key2)
|
||||||
@@ -155,4 +156,3 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
|
|||||||
_, err = transaction.Get(key)
|
_, err = transaction.Get(key)
|
||||||
c.Assert(err, NotNil)
|
c.Assert(err, NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func (s *EtcDStorage) Close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reopen tries to open (re-open) the database
|
// Open returns the database
|
||||||
func (s *EtcDStorage) Open() error {
|
func (s *EtcDStorage) Open() error {
|
||||||
if s.db != nil {
|
if s.db != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -67,8 +67,7 @@ func (t *transaction) Commit() (err error) {
|
|||||||
// Discard is safe to call after Commit(), it would be no-op
|
// Discard is safe to call after Commit(), it would be no-op
|
||||||
func (t *transaction) Discard() {
|
func (t *transaction) Discard() {
|
||||||
t.ops = []clientv3.Op{}
|
t.ops = []clientv3.Op{}
|
||||||
t.tmpdb.Drop()
|
_ = t.tmpdb.Drop()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// transaction should implement database.Transaction
|
// transaction should implement database.Transaction
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ func RecoverDB(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Close()
|
_ = db.Close()
|
||||||
stor.Close()
|
_ = stor.Close()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,17 +119,17 @@ func (s *LevelDBSuite) TestDelete(c *C) {
|
|||||||
func (s *LevelDBSuite) TestByPrefix(c *C) {
|
func (s *LevelDBSuite) TestByPrefix(c *C) {
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
||||||
|
|
||||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
_ = s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
_ = s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||||
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
_ = s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
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}})
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
_ = s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
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}})
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
_ = s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
||||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
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}})
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ func (s *LevelDBSuite) TestHasPrefix(c *C) {
|
|||||||
c.Check(s.db.HasPrefix([]byte(nil)), Equals, false)
|
c.Check(s.db.HasPrefix([]byte(nil)), Equals, false)
|
||||||
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, false)
|
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, false)
|
||||||
|
|
||||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
_ = s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
|
|
||||||
c.Check(s.db.HasPrefix([]byte(nil)), Equals, true)
|
c.Check(s.db.HasPrefix([]byte(nil)), Equals, true)
|
||||||
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true)
|
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true)
|
||||||
@@ -180,8 +180,8 @@ func (s *LevelDBSuite) TestBatch(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
batch := s.db.CreateBatch()
|
batch := s.db.CreateBatch()
|
||||||
batch.Put(key2, value2)
|
_ = batch.Put(key2, value2)
|
||||||
batch.Delete(key)
|
_ = batch.Delete(key)
|
||||||
|
|
||||||
v, err := s.db.Get(key)
|
v, err := s.db.Get(key)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
@@ -202,9 +202,9 @@ func (s *LevelDBSuite) TestBatch(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LevelDBSuite) TestCompactDB(c *C) {
|
func (s *LevelDBSuite) TestCompactDB(c *C) {
|
||||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
_ = s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
_ = s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||||
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
_ = s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||||
|
|
||||||
c.Check(s.db.CompactDB(), IsNil)
|
c.Check(s.db.CompactDB(), IsNil)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -60,14 +60,14 @@ func (c *Changes) VerifyAndParse(acceptUnsigned, ignoreSignature bool, verifier
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer input.Close()
|
defer func() { _ = input.Close() }()
|
||||||
|
|
||||||
isClearSigned, err := verifier.IsClearSigned(input)
|
isClearSigned, err := verifier.IsClearSigned(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
input.Seek(0, 0)
|
_, _ = input.Seek(0, 0)
|
||||||
|
|
||||||
if !isClearSigned && !acceptUnsigned {
|
if !isClearSigned && !acceptUnsigned {
|
||||||
return fmt.Errorf(".changes file is not signed and unsigned processing hasn't been enabled")
|
return fmt.Errorf(".changes file is not signed and unsigned processing hasn't been enabled")
|
||||||
@@ -79,7 +79,7 @@ func (c *Changes) VerifyAndParse(acceptUnsigned, ignoreSignature bool, verifier
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
input.Seek(0, 0)
|
_, _ = input.Seek(0, 0)
|
||||||
|
|
||||||
c.SignatureKeys = keyInfo.GoodKeys
|
c.SignatureKeys = keyInfo.GoodKeys
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ func (c *Changes) VerifyAndParse(acceptUnsigned, ignoreSignature bool, verifier
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer text.Close()
|
defer func() { _ = text.Close() }()
|
||||||
} else {
|
} else {
|
||||||
text = input
|
text = input
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ func ImportChangesFiles(changesFiles []string, reporter aptly.ResultReporter, ac
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
failedFiles = append(failedFiles, path)
|
failedFiles = append(failedFiles, path)
|
||||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||||
changes.Cleanup()
|
_ = changes.Cleanup()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ func ImportChangesFiles(changesFiles []string, reporter aptly.ResultReporter, ac
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
failedFiles = append(failedFiles, path)
|
failedFiles = append(failedFiles, path)
|
||||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||||
changes.Cleanup()
|
_ = changes.Cleanup()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ func ImportChangesFiles(changesFiles []string, reporter aptly.ResultReporter, ac
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
failedFiles = append(failedFiles, path)
|
failedFiles = append(failedFiles, path)
|
||||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||||
changes.Cleanup()
|
_ = changes.Cleanup()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +354,7 @@ func ImportChangesFiles(changesFiles []string, reporter aptly.ResultReporter, ac
|
|||||||
failedFiles = append(failedFiles, path)
|
failedFiles = append(failedFiles, path)
|
||||||
reporter.Warning("changes file skipped due to uploaders config: %s, keys %#v: %s",
|
reporter.Warning("changes file skipped due to uploaders config: %s, keys %#v: %s",
|
||||||
changes.ChangesName, changes.SignatureKeys, err)
|
changes.ChangesName, changes.SignatureKeys, err)
|
||||||
changes.Cleanup()
|
_ = changes.Cleanup()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -51,7 +51,7 @@ func (s *ChangesSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
func (s *ChangesSuite) TearDownTest(c *C) {
|
func (s *ChangesSuite) TearDownTest(c *C) {
|
||||||
s.progress.Shutdown()
|
s.progress.Shutdown()
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChangesSuite) TestParseAndVerify(c *C) {
|
func (s *ChangesSuite) TestParseAndVerify(c *C) {
|
||||||
@@ -108,13 +108,13 @@ func (s *ChangesSuite) TestImportChangesFiles(c *C) {
|
|||||||
|
|
||||||
for _, path := range origFailedFiles {
|
for _, path := range origFailedFiles {
|
||||||
filename := filepath.Join(s.Dir, filepath.Base(path))
|
filename := filepath.Join(s.Dir, filepath.Base(path))
|
||||||
utils.CopyFile(path, filename)
|
_ = utils.CopyFile(path, filename)
|
||||||
expectedFailedFiles = append(expectedFailedFiles, filename)
|
expectedFailedFiles = append(expectedFailedFiles, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range origProcessedFiles {
|
for _, path := range origProcessedFiles {
|
||||||
filename := filepath.Join(s.Dir, filepath.Base(path))
|
filename := filepath.Join(s.Dir, filepath.Base(path))
|
||||||
utils.CopyFile(path, filename)
|
_ = utils.CopyFile(path, filename)
|
||||||
expectedProcessedFiles = append(expectedProcessedFiles, filename)
|
expectedProcessedFiles = append(expectedProcessedFiles, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (s *ChecksumCollectionSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChecksumCollectionSuite) TearDownTest(c *C) {
|
func (s *ChecksumCollectionSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChecksumCollectionSuite) TestFlow(c *C) {
|
func (s *ChecksumCollectionSuite) TestFlow(c *C) {
|
||||||
|
|||||||
+10
-10
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/aptly-dev/aptly/pgp"
|
"github.com/aptly-dev/aptly/pgp"
|
||||||
"github.com/kjk/lzma"
|
"github.com/kjk/lzma"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
"github.com/smira/go-xz"
|
xz "github.com/smira/go-xz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Source kinds
|
// Source kinds
|
||||||
@@ -35,7 +35,7 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
library := ar.NewReader(file)
|
library := ar.NewReader(file)
|
||||||
for {
|
for {
|
||||||
@@ -66,14 +66,14 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "unable to ungzip %s from %s", header.Name, packageFile)
|
return nil, errors.Wrapf(err, "unable to ungzip %s from %s", header.Name, packageFile)
|
||||||
}
|
}
|
||||||
defer ungzip.Close()
|
defer func() { _ = ungzip.Close() }()
|
||||||
tarInput = ungzip
|
tarInput = ungzip
|
||||||
case "control.tar.xz":
|
case "control.tar.xz":
|
||||||
unxz, err := xz.NewReader(bufReader)
|
unxz, err := xz.NewReader(bufReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "unable to unxz %s from %s", header.Name, packageFile)
|
return nil, errors.Wrapf(err, "unable to unxz %s from %s", header.Name, packageFile)
|
||||||
}
|
}
|
||||||
defer unxz.Close()
|
defer func() { _ = unxz.Close() }()
|
||||||
tarInput = unxz
|
tarInput = unxz
|
||||||
case "control.tar.zst":
|
case "control.tar.zst":
|
||||||
unzstd, err := zstd.NewReader(bufReader)
|
unzstd, err := zstd.NewReader(bufReader)
|
||||||
@@ -116,10 +116,10 @@ func GetControlFileFromDsc(dscFile string, verifier pgp.Verifier) (Stanza, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
isClearSigned, err := verifier.IsClearSigned(file)
|
isClearSigned, err := verifier.IsClearSigned(file)
|
||||||
file.Seek(0, 0)
|
_, _ = file.Seek(0, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -132,7 +132,7 @@ func GetControlFileFromDsc(dscFile string, verifier pgp.Verifier) (Stanza, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer text.Close()
|
defer func() { _ = text.Close() }()
|
||||||
} else {
|
} else {
|
||||||
text = file
|
text = file
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ func GetContentsFromDeb(file io.Reader, packageFile string) ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "unable to ungzip data.tar.gz from %s", packageFile)
|
return nil, errors.Wrapf(err, "unable to ungzip data.tar.gz from %s", packageFile)
|
||||||
}
|
}
|
||||||
defer ungzip.Close()
|
defer func() { _ = ungzip.Close() }()
|
||||||
tarInput = ungzip
|
tarInput = ungzip
|
||||||
}
|
}
|
||||||
case "data.tar.bz2":
|
case "data.tar.bz2":
|
||||||
@@ -191,11 +191,11 @@ func GetContentsFromDeb(file io.Reader, packageFile string) ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "unable to unxz data.tar.xz from %s", packageFile)
|
return nil, errors.Wrapf(err, "unable to unxz data.tar.xz from %s", packageFile)
|
||||||
}
|
}
|
||||||
defer unxz.Close()
|
defer func() { _ = unxz.Close() }()
|
||||||
tarInput = unxz
|
tarInput = unxz
|
||||||
case "data.tar.lzma":
|
case "data.tar.lzma":
|
||||||
unlzma := lzma.NewReader(bufReader)
|
unlzma := lzma.NewReader(bufReader)
|
||||||
defer unlzma.Close()
|
defer func() { _ = unlzma.Close() }()
|
||||||
tarInput = unlzma
|
tarInput = unlzma
|
||||||
case "data.tar.zst":
|
case "data.tar.zst":
|
||||||
unzstd, err := zstd.NewReader(bufReader)
|
unzstd, err := zstd.NewReader(bufReader)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package deb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindDanglingReferences finds references that exist in the given PackageRefList, but not in the given PackageCollection.
|
||||||
|
// It returns all such references, so they can be removed from the database.
|
||||||
|
func FindDanglingReferences(reflist *PackageRefList, packages *PackageCollection) (dangling *PackageRefList, err error) {
|
||||||
|
dangling = &PackageRefList{}
|
||||||
|
|
||||||
|
err = reflist.ForEach(func(key []byte) error {
|
||||||
|
ok, err := isDangling(packages, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
dangling.Refs = append(dangling.Refs, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dangling, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDangling(packages *PackageCollection, key []byte) (bool, error) {
|
||||||
|
_, err := packages.ByKey(key)
|
||||||
|
if errors.Is(err, database.ErrNotFound) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("get reference %q: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package deb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindDanglingReferences(t *testing.T) {
|
||||||
|
reflist := deb.NewPackageRefList()
|
||||||
|
reflist.Refs = [][]byte{[]byte("P existing 1.2.3"), []byte("P dangling 1.2.3")}
|
||||||
|
|
||||||
|
db, _ := goleveldb.NewOpenDB(t.TempDir())
|
||||||
|
packages := deb.NewPackageCollection(db)
|
||||||
|
|
||||||
|
if err := packages.Update(&deb.Package{Name: "existing", Version: "1.2.3"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dangling, err := deb.FindDanglingReferences(reflist, packages)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := &deb.PackageRefList{
|
||||||
|
Refs: [][]byte{[]byte("P dangling 1.2.3")},
|
||||||
|
}
|
||||||
|
|
||||||
|
compareRefs(t, exp, dangling)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareRefs(t *testing.T, exp, got *deb.PackageRefList) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if len(exp.Refs) != len(got.Refs) {
|
||||||
|
t.Fatalf("refs length mismatch: exp %d, got %d", len(exp.Refs), len(got.Refs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range exp.Refs {
|
||||||
|
if !bytes.Equal(exp.Refs[i], got.Refs[i]) {
|
||||||
|
t.Fatalf("refs do not match: exp %q, got %q", exp.Refs[i], got.Refs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,8 +26,11 @@ var (
|
|||||||
"Version",
|
"Version",
|
||||||
"Codename",
|
"Codename",
|
||||||
"Date",
|
"Date",
|
||||||
|
"Valid-Until",
|
||||||
"NotAutomatic",
|
"NotAutomatic",
|
||||||
"ButAutomaticUpgrades",
|
"ButAutomaticUpgrades",
|
||||||
|
"Acquire-By-Hash",
|
||||||
|
"Signed-By",
|
||||||
"Architectures",
|
"Architectures",
|
||||||
"Architecture",
|
"Architecture",
|
||||||
"Components",
|
"Components",
|
||||||
|
|||||||
+1
-1
@@ -163,7 +163,7 @@ func (s *ControlFileSuite) TestCanonicalCase(c *C) {
|
|||||||
func (s *ControlFileSuite) TestLongFields(c *C) {
|
func (s *ControlFileSuite) TestLongFields(c *C) {
|
||||||
f, err := os.Open("long.stanza")
|
f, err := os.Open("long.stanza")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer f.Close()
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
r := NewControlFileReader(f, false, false)
|
r := NewControlFileReader(f, false, false)
|
||||||
stanza, e := r.ReadStanza()
|
stanza, e := r.ReadStanza()
|
||||||
|
|||||||
+11
-11
@@ -12,15 +12,15 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
graph := gographviz.NewEscape()
|
graph := gographviz.NewEscape()
|
||||||
graph.SetDir(true)
|
_ = graph.SetDir(true)
|
||||||
graph.SetName("aptly")
|
_ = graph.SetName("aptly")
|
||||||
|
|
||||||
var labelStart string
|
var labelStart string
|
||||||
var labelEnd string
|
var labelEnd string
|
||||||
|
|
||||||
switch layout {
|
switch layout {
|
||||||
case "vertical":
|
case "vertical":
|
||||||
graph.AddAttr("aptly", "rankdir", "LR")
|
_ = graph.AddAttr("aptly", "rankdir", "LR")
|
||||||
labelStart = ""
|
labelStart = ""
|
||||||
labelEnd = ""
|
labelEnd = ""
|
||||||
case "horizontal":
|
case "horizontal":
|
||||||
@@ -38,7 +38,7 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
_ = graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||||
"shape": "Mrecord",
|
"shape": "Mrecord",
|
||||||
"style": "filled",
|
"style": "filled",
|
||||||
"fillcolor": "darkgoldenrod1",
|
"fillcolor": "darkgoldenrod1",
|
||||||
@@ -60,7 +60,7 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
_ = graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||||
"shape": "Mrecord",
|
"shape": "Mrecord",
|
||||||
"style": "filled",
|
"style": "filled",
|
||||||
"fillcolor": "mediumseagreen",
|
"fillcolor": "mediumseagreen",
|
||||||
@@ -75,7 +75,7 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error {
|
_ = collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error {
|
||||||
existingNodes[snapshot.UUID] = true
|
existingNodes[snapshot.UUID] = true
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -91,7 +91,7 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
description = "Snapshot from repo"
|
description = "Snapshot from repo"
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.AddNode("aptly", snapshot.UUID, map[string]string{
|
_ = graph.AddNode("aptly", snapshot.UUID, map[string]string{
|
||||||
"shape": "Mrecord",
|
"shape": "Mrecord",
|
||||||
"style": "filled",
|
"style": "filled",
|
||||||
"fillcolor": "cadetblue1",
|
"fillcolor": "cadetblue1",
|
||||||
@@ -103,7 +103,7 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
for _, uuid := range snapshot.SourceIDs {
|
for _, uuid := range snapshot.SourceIDs {
|
||||||
_, exists := existingNodes[uuid]
|
_, exists := existingNodes[uuid]
|
||||||
if exists {
|
if exists {
|
||||||
graph.AddEdge(uuid, snapshot.UUID, true, nil)
|
_ = graph.AddEdge(uuid, snapshot.UUID, true, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,8 +114,8 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionFactory.PublishedRepoCollection().ForEach(func(repo *PublishedRepo) error {
|
_ = collectionFactory.PublishedRepoCollection().ForEach(func(repo *PublishedRepo) error {
|
||||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
_ = graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||||
"shape": "Mrecord",
|
"shape": "Mrecord",
|
||||||
"style": "filled",
|
"style": "filled",
|
||||||
"fillcolor": "darkolivegreen1",
|
"fillcolor": "darkolivegreen1",
|
||||||
@@ -127,7 +127,7 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
for _, uuid := range repo.Sources {
|
for _, uuid := range repo.Sources {
|
||||||
_, exists := existingNodes[uuid]
|
_, exists := existingNodes[uuid]
|
||||||
if exists {
|
if exists {
|
||||||
graph.AddEdge(uuid, repo.UUID, true, nil)
|
_ = graph.AddEdge(uuid, repo.UUID, true, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -59,24 +59,24 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
|||||||
if file.discardable {
|
if file.discardable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
file.BufWriter()
|
_, _ = file.BufWriter()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := file.w.Flush()
|
err := file.w.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
file.tempFile.Close()
|
_ = file.tempFile.Close()
|
||||||
return fmt.Errorf("unable to write to index file: %s", err)
|
return fmt.Errorf("unable to write to index file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.compressable {
|
if file.compressable {
|
||||||
err = utils.CompressFile(file.tempFile, file.onlyGzip || file.parent.skipBz2)
|
err = utils.CompressFile(file.tempFile, file.onlyGzip || file.parent.skipBz2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
file.tempFile.Close()
|
_ = file.tempFile.Close()
|
||||||
return fmt.Errorf("unable to compress index file: %s", err)
|
return fmt.Errorf("unable to compress index file: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file.tempFile.Close()
|
_ = file.tempFile.Close()
|
||||||
|
|
||||||
exts := []string{""}
|
exts := []string{""}
|
||||||
cksumExts := exts
|
cksumExts := exts
|
||||||
@@ -220,11 +220,11 @@ func packageIndexByHash(file *indexFile, ext string, hash string, sum string) er
|
|||||||
// If we managed to resolve the link target: delete it. This is the
|
// If we managed to resolve the link target: delete it. This is the
|
||||||
// oldest physical index file we no longer need. Once we drop our
|
// oldest physical index file we no longer need. Once we drop our
|
||||||
// old symlink we'll essentially forget about it existing at all.
|
// old symlink we'll essentially forget about it existing at all.
|
||||||
file.parent.publishedStorage.Remove(linkTarget)
|
_ = file.parent.publishedStorage.Remove(linkTarget)
|
||||||
}
|
}
|
||||||
file.parent.publishedStorage.Remove(oldIndexPath)
|
_ = file.parent.publishedStorage.Remove(oldIndexPath)
|
||||||
}
|
}
|
||||||
file.parent.publishedStorage.RenameFile(indexPath, oldIndexPath)
|
_ = file.parent.publishedStorage.RenameFile(indexPath, oldIndexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create symlink
|
// create symlink
|
||||||
|
|||||||
+35
-2
@@ -172,6 +172,39 @@ func (l *PackageList) ForEach(handler func(*Package) error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterLatest creates a copy of the package list containing only the
|
||||||
|
// latest version for each package name/architecture pair.
|
||||||
|
func (l *PackageList) FilterLatest() (*PackageList, error) {
|
||||||
|
if l == nil {
|
||||||
|
return nil, fmt.Errorf("package list is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make(map[string]*Package, l.Len())
|
||||||
|
|
||||||
|
err := l.ForEach(func(p *Package) error {
|
||||||
|
key := p.Architecture + "|" + p.Name
|
||||||
|
|
||||||
|
if existing, found := filtered[key]; !found || CompareVersions(p.Version, existing.Version) > 0 {
|
||||||
|
filtered[key] = p
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := NewPackageListWithDuplicates(l.duplicatesAllowed, len(filtered))
|
||||||
|
|
||||||
|
for _, pkg := range filtered {
|
||||||
|
if err = result.Add(pkg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ForEachIndexed calls handler for each package in list in indexed order
|
// ForEachIndexed calls handler for each package in list in indexed order
|
||||||
func (l *PackageList) ForEachIndexed(handler func(*Package) error) error {
|
func (l *PackageList) ForEachIndexed(handler func(*Package) error) error {
|
||||||
if !l.indexed {
|
if !l.indexed {
|
||||||
@@ -438,7 +471,7 @@ func (l *PackageList) Scan(q PackageQuery) (result *PackageList) {
|
|||||||
result = NewPackageListWithDuplicates(l.duplicatesAllowed, 0)
|
result = NewPackageListWithDuplicates(l.duplicatesAllowed, 0)
|
||||||
for _, pkg := range l.packages {
|
for _, pkg := range l.packages {
|
||||||
if q.Matches(pkg) {
|
if q.Matches(pkg) {
|
||||||
result.Add(pkg)
|
_ = result.Add(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +489,7 @@ func (l *PackageList) SearchByKey(arch, name, version string) (result *PackageLi
|
|||||||
|
|
||||||
pkg := l.packages["P"+arch+" "+name+" "+version]
|
pkg := l.packages["P"+arch+" "+name+" "+version]
|
||||||
if pkg != nil {
|
if pkg != nil {
|
||||||
result.Add(pkg)
|
_ = result.Add(pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+59
-10
@@ -96,7 +96,7 @@ func (s *PackageListSuite) SetUpTest(c *C) {
|
|||||||
{Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
|
{Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
|
||||||
}
|
}
|
||||||
for _, p := range s.packages {
|
for _, p := range s.packages {
|
||||||
s.il.Add(p)
|
_ = s.il.Add(p)
|
||||||
}
|
}
|
||||||
s.il.PrepareIndex()
|
s.il.PrepareIndex()
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ func (s *PackageListSuite) SetUpTest(c *C) {
|
|||||||
{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||||
}
|
}
|
||||||
for _, p := range s.packages2 {
|
for _, p := range s.packages2 {
|
||||||
s.il2.Add(p)
|
_ = s.il2.Add(p)
|
||||||
}
|
}
|
||||||
s.il2.PrepareIndex()
|
s.il2.PrepareIndex()
|
||||||
|
|
||||||
@@ -202,8 +202,8 @@ func (s *PackageListSuite) TestRemoveWhenIndexed(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageListSuite) TestForeach(c *C) {
|
func (s *PackageListSuite) TestForeach(c *C) {
|
||||||
s.list.Add(s.p1)
|
_ = s.list.Add(s.p1)
|
||||||
s.list.Add(s.p3)
|
_ = s.list.Add(s.p3)
|
||||||
|
|
||||||
Len := 0
|
Len := 0
|
||||||
err := s.list.ForEach(func(*Package) error {
|
err := s.list.ForEach(func(*Package) error {
|
||||||
@@ -232,21 +232,21 @@ func (s *PackageListSuite) TestIndex(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageListSuite) TestAppend(c *C) {
|
func (s *PackageListSuite) TestAppend(c *C) {
|
||||||
s.list.Add(s.p1)
|
_ = s.list.Add(s.p1)
|
||||||
s.list.Add(s.p3)
|
_ = s.list.Add(s.p3)
|
||||||
|
|
||||||
err := s.list.Append(s.il)
|
err := s.list.Append(s.il)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(s.list.Len(), Equals, 16)
|
c.Check(s.list.Len(), Equals, 16)
|
||||||
|
|
||||||
list := NewPackageList()
|
list := NewPackageList()
|
||||||
list.Add(s.p4)
|
_ = list.Add(s.p4)
|
||||||
|
|
||||||
err = s.list.Append(list)
|
err = s.list.Append(list)
|
||||||
c.Check(err, ErrorMatches, "package already exists and is different: .*")
|
c.Check(err, ErrorMatches, "package already exists and is different: .*")
|
||||||
|
|
||||||
s.list.PrepareIndex()
|
s.list.PrepareIndex()
|
||||||
c.Check(func() { s.list.Append(s.il) }, Panics, "Append not supported when indexed")
|
c.Check(func() { _ = s.list.Append(s.il) }, Panics, "Append not supported when indexed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageListSuite) TestSearch(c *C) {
|
func (s *PackageListSuite) TestSearch(c *C) {
|
||||||
@@ -312,7 +312,7 @@ func (s *PackageListSuite) TestSearch(c *C) {
|
|||||||
|
|
||||||
func (s *PackageListSuite) TestFilter(c *C) {
|
func (s *PackageListSuite) TestFilter(c *C) {
|
||||||
c.Check(func() {
|
c.Check(func() {
|
||||||
s.list.Filter(FilterOptions{
|
_, _ = s.list.Filter(FilterOptions{
|
||||||
Queries: []PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}},
|
Queries: []PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}},
|
||||||
})
|
})
|
||||||
}, Panics, "list not indexed, can't filter")
|
}, Panics, "list not indexed, can't filter")
|
||||||
@@ -479,7 +479,7 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) {
|
|||||||
{Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}})
|
{Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}})
|
||||||
|
|
||||||
for _, p := range s.sourcePackages {
|
for _, p := range s.sourcePackages {
|
||||||
s.il.Add(p)
|
_ = s.il.Add(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"i386", "amd64"}, s.il, nil)
|
missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"i386", "amd64"}, s.il, nil)
|
||||||
@@ -503,3 +503,52 @@ func (s *PackageListSuite) TestArchitectures(c *C) {
|
|||||||
sort.Strings(archs)
|
sort.Strings(archs)
|
||||||
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390"})
|
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PackageListSuite) TestFilterLatest(c *C) {
|
||||||
|
list := NewPackageList()
|
||||||
|
|
||||||
|
older := packageStanza.Copy()
|
||||||
|
older["Version"] = "1.0"
|
||||||
|
olderPkg := NewPackageFromControlFile(older)
|
||||||
|
_ = list.Add(olderPkg)
|
||||||
|
|
||||||
|
newer := packageStanza.Copy()
|
||||||
|
newer["Version"] = "2.0"
|
||||||
|
newerPkg := NewPackageFromControlFile(newer)
|
||||||
|
_ = list.Add(newerPkg)
|
||||||
|
|
||||||
|
shared := packageStanza.Copy()
|
||||||
|
shared["Architecture"] = ArchitectureAll
|
||||||
|
shared["Version"] = "3.0"
|
||||||
|
shared["Package"] = "shared"
|
||||||
|
sharedPkg := NewPackageFromControlFile(shared)
|
||||||
|
_ = list.Add(sharedPkg)
|
||||||
|
|
||||||
|
filtered, err := list.FilterLatest()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(filtered.Len(), Equals, 2)
|
||||||
|
c.Check(filtered.Has(newerPkg), Equals, true)
|
||||||
|
c.Check(filtered.Has(sharedPkg), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackageListSuite) TestFilterLatestPreservesDuplicatesFlag(c *C) {
|
||||||
|
list := NewPackageListWithDuplicates(true, 2)
|
||||||
|
|
||||||
|
_ = list.Add(NewPackageFromControlFile(packageStanza.Copy()))
|
||||||
|
|
||||||
|
another := packageStanza.Copy()
|
||||||
|
another["Version"] = "7.41-1"
|
||||||
|
_ = list.Add(NewPackageFromControlFile(another))
|
||||||
|
|
||||||
|
filtered, err := list.FilterLatest()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(filtered.duplicatesAllowed, Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackageListSuite) TestFilterLatestNil(c *C) {
|
||||||
|
var list *PackageList
|
||||||
|
|
||||||
|
filtered, err := list.FilterLatest()
|
||||||
|
c.Assert(err, ErrorMatches, "package list is nil")
|
||||||
|
c.Assert(filtered, IsNil)
|
||||||
|
}
|
||||||
|
|||||||
+8
-7
@@ -69,7 +69,7 @@ func (repo *LocalRepo) Encode() []byte {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||||
encoder.Encode(repo)
|
_ = encoder.Encode(repo)
|
||||||
|
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ func (collection *LocalRepoCollection) search(filter func(*LocalRepo) bool, uniq
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.db.ProcessByPrefix([]byte("L"), func(_, blob []byte) error {
|
_ = collection.db.ProcessByPrefix([]byte("L"), func(_, blob []byte) error {
|
||||||
r := &LocalRepo{}
|
r := &LocalRepo{}
|
||||||
if err := r.Decode(blob); err != nil {
|
if err := r.Decode(blob); err != nil {
|
||||||
log.Printf("Error decoding local repo: %s\n", err)
|
log.Printf("Error decoding local repo: %s\n", err)
|
||||||
@@ -159,15 +159,17 @@ func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
|||||||
// Update stores updated information about repo in DB
|
// Update stores updated information about repo in DB
|
||||||
func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
||||||
batch := collection.db.CreateBatch()
|
batch := collection.db.CreateBatch()
|
||||||
batch.Put(repo.Key(), repo.Encode())
|
_ = batch.Put(repo.Key(), repo.Encode())
|
||||||
if repo.packageRefs != nil {
|
if repo.packageRefs != nil {
|
||||||
batch.Put(repo.RefKey(), repo.packageRefs.Encode())
|
_ = batch.Put(repo.RefKey(), repo.packageRefs.Encode())
|
||||||
}
|
}
|
||||||
return batch.Write()
|
return batch.Write()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadComplete loads additional information for local repo
|
// LoadComplete loads additional information for local repo
|
||||||
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
||||||
|
repo.packageRefs = &PackageRefList{}
|
||||||
|
|
||||||
encoded, err := collection.db.Get(repo.RefKey())
|
encoded, err := collection.db.Get(repo.RefKey())
|
||||||
if err == database.ErrNotFound {
|
if err == database.ErrNotFound {
|
||||||
return nil
|
return nil
|
||||||
@@ -176,7 +178,6 @@ func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.packageRefs = &PackageRefList{}
|
|
||||||
return repo.packageRefs.Decode(encoded)
|
return repo.packageRefs.Decode(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +248,7 @@ func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
|||||||
delete(collection.cache, repo.UUID)
|
delete(collection.cache, repo.UUID)
|
||||||
|
|
||||||
batch := collection.db.CreateBatch()
|
batch := collection.db.CreateBatch()
|
||||||
batch.Delete(repo.Key())
|
_ = batch.Delete(repo.Key())
|
||||||
batch.Delete(repo.RefKey())
|
_ = batch.Delete(repo.RefKey())
|
||||||
return batch.Write()
|
return batch.Write()
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-10
@@ -21,8 +21,8 @@ var _ = Suite(&LocalRepoSuite{})
|
|||||||
func (s *LocalRepoSuite) SetUpTest(c *C) {
|
func (s *LocalRepoSuite) SetUpTest(c *C) {
|
||||||
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
||||||
s.list = NewPackageList()
|
s.list = NewPackageList()
|
||||||
s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"})
|
_ = s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"})
|
||||||
s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"})
|
_ = s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"})
|
||||||
|
|
||||||
s.reflist = NewPackageRefListFromPackageList(s.list)
|
s.reflist = NewPackageRefListFromPackageList(s.list)
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ func (s *LocalRepoSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRepoSuite) TearDownTest(c *C) {
|
func (s *LocalRepoSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRepoSuite) TestString(c *C) {
|
func (s *LocalRepoSuite) TestString(c *C) {
|
||||||
@@ -88,14 +88,14 @@ func (s *LocalRepoCollectionSuite) SetUpTest(c *C) {
|
|||||||
s.collection = NewLocalRepoCollection(s.db)
|
s.collection = NewLocalRepoCollection(s.db)
|
||||||
|
|
||||||
s.list = NewPackageList()
|
s.list = NewPackageList()
|
||||||
s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"})
|
_ = s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"})
|
||||||
s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"})
|
_ = s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"})
|
||||||
|
|
||||||
s.reflist = NewPackageRefListFromPackageList(s.list)
|
s.reflist = NewPackageRefListFromPackageList(s.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRepoCollectionSuite) TearDownTest(c *C) {
|
func (s *LocalRepoCollectionSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRepoCollectionSuite) TestAddByName(c *C) {
|
func (s *LocalRepoCollectionSuite) TestAddByName(c *C) {
|
||||||
@@ -133,6 +133,18 @@ func (s *LocalRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
c.Assert(r.String(), Equals, repo.String())
|
c.Assert(r.String(), Equals, repo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LocalRepoCollectionSuite) TestLoadCompleteNoRefKey(c *C) {
|
||||||
|
repo := NewLocalRepo("local1", "Comment 1")
|
||||||
|
c.Assert(s.collection.Update(repo), IsNil)
|
||||||
|
|
||||||
|
r, err := s.collection.ByName("local1")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Assert(s.collection.LoadComplete(r), IsNil)
|
||||||
|
c.Assert(r.packageRefs, NotNil)
|
||||||
|
c.Assert(r.NumPackages(), Equals, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LocalRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
func (s *LocalRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||||
repo := NewLocalRepo("local1", "Comment 1")
|
repo := NewLocalRepo("local1", "Comment 1")
|
||||||
c.Assert(s.collection.Update(repo), IsNil)
|
c.Assert(s.collection.Update(repo), IsNil)
|
||||||
@@ -156,7 +168,7 @@ func (s *LocalRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
|||||||
|
|
||||||
func (s *LocalRepoCollectionSuite) TestForEachAndLen(c *C) {
|
func (s *LocalRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||||
repo := NewLocalRepo("local1", "Comment 1")
|
repo := NewLocalRepo("local1", "Comment 1")
|
||||||
s.collection.Add(repo)
|
_ = s.collection.Add(repo)
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
err := s.collection.ForEach(func(*LocalRepo) error {
|
err := s.collection.ForEach(func(*LocalRepo) error {
|
||||||
@@ -178,10 +190,10 @@ func (s *LocalRepoCollectionSuite) TestForEachAndLen(c *C) {
|
|||||||
|
|
||||||
func (s *LocalRepoCollectionSuite) TestDrop(c *C) {
|
func (s *LocalRepoCollectionSuite) TestDrop(c *C) {
|
||||||
repo1 := NewLocalRepo("local1", "Comment 1")
|
repo1 := NewLocalRepo("local1", "Comment 1")
|
||||||
s.collection.Add(repo1)
|
_ = s.collection.Add(repo1)
|
||||||
|
|
||||||
repo2 := NewLocalRepo("local2", "Comment 2")
|
repo2 := NewLocalRepo("local2", "Comment 2")
|
||||||
s.collection.Add(repo2)
|
_ = s.collection.Add(repo2)
|
||||||
|
|
||||||
r1, _ := s.collection.ByUUID(repo1.UUID)
|
r1, _ := s.collection.ByUUID(repo1.UUID)
|
||||||
c.Check(r1, Equals, repo1)
|
c.Check(r1, Equals, repo1)
|
||||||
@@ -208,6 +220,6 @@ func (s *LocalRepoCollectionSuite) TestDropNonExisting(c *C) {
|
|||||||
_, err := s.collection.ByUUID(repo.UUID)
|
_, err := s.collection.ByUUID(repo.UUID)
|
||||||
c.Check(err, ErrorMatches, "local repo .* not found")
|
c.Check(err, ErrorMatches, "local repo .* not found")
|
||||||
|
|
||||||
err = s.collection.Drop(repo)
|
_ = s.collection.Drop(repo)
|
||||||
c.Check(s.collection.Drop(repo), ErrorMatches, "local repo not found")
|
c.Check(s.collection.Drop(repo), ErrorMatches, "local repo not found")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -565,7 +565,7 @@ func (p *Package) CalculateContents(packagePool aptly.PackagePool, progress aptl
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer func() { _ = reader.Close() }()
|
||||||
|
|
||||||
contents, err := GetContentsFromDeb(reader, file.Filename)
|
contents, err := GetContentsFromDeb(reader, file.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if q.Matches(pkg) {
|
if q.Matches(pkg) {
|
||||||
result.Add(pkg)
|
_ = result.Add(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,7 +337,7 @@ func (collection *PackageCollection) SearchByKey(arch, name, version string) (re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version {
|
if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version {
|
||||||
result.Add(pkg)
|
_ = result.Add(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (s *PackageCollectionSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageCollectionSuite) TearDownTest(c *C) {
|
func (s *PackageCollectionSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageCollectionSuite) TestUpdate(c *C) {
|
func (s *PackageCollectionSuite) TestUpdate(c *C) {
|
||||||
@@ -67,7 +67,7 @@ func (s *PackageCollectionSuite) TestByKey(c *C) {
|
|||||||
|
|
||||||
func (s *PackageCollectionSuite) TestByKeyOld0_3(c *C) {
|
func (s *PackageCollectionSuite) TestByKeyOld0_3(c *C) {
|
||||||
key := []byte("Pi386 vmware-view-open-client 4.5.0-297975+dfsg-4+b1")
|
key := []byte("Pi386 vmware-view-open-client 4.5.0-297975+dfsg-4+b1")
|
||||||
s.db.Put(key, old0_3Package)
|
_ = s.db.Put(key, old0_3Package)
|
||||||
|
|
||||||
p, err := s.collection.ByKey(key)
|
p, err := s.collection.ByKey(key)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (files PackageFiles) Hash() uint64 {
|
|||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
h.Write([]byte(f.Filename))
|
h.Write([]byte(f.Filename))
|
||||||
binary.Write(h, binary.BigEndian, f.Checksums.Size)
|
_ = binary.Write(h, binary.BigEndian, f.Checksums.Size)
|
||||||
h.Write([]byte(f.Checksums.MD5))
|
h.Write([]byte(f.Checksums.MD5))
|
||||||
h.Write([]byte(f.Checksums.SHA1))
|
h.Write([]byte(f.Checksums.SHA1))
|
||||||
h.Write([]byte(f.Checksums.SHA256))
|
h.Write([]byte(f.Checksums.SHA256))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package deb
|
package deb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
@@ -39,7 +39,7 @@ func (s *PackageFilesSuite) TestVerify(c *C) {
|
|||||||
c.Check(result, Equals, false)
|
c.Check(result, Equals, false)
|
||||||
|
|
||||||
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
||||||
c.Assert(ioutil.WriteFile(tmpFilepath, []byte("abcde"), 0777), IsNil)
|
c.Assert(os.WriteFile(tmpFilepath, []byte("abcde"), 0777), IsNil)
|
||||||
|
|
||||||
s.files[0].PoolPath, _ = packagePool.Import(tmpFilepath, s.files[0].Filename, &s.files[0].Checksums, false, s.cs)
|
s.files[0].PoolPath, _ = packagePool.Import(tmpFilepath, s.files[0].Filename, &s.files[0].Checksums, false, s.cs)
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -2,7 +2,7 @@ package deb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ func (s *PackageSuite) TestNewUdebFromPara(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageSuite) TestNewInstallerFromPara(c *C) {
|
func (s *PackageSuite) TestNewInstallerFromPara(c *C) {
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://example.com/debian", "squeeze", []string{"main"}, []string{}, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://example.com/debian", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
||||||
downloader := http.NewFakeDownloader()
|
downloader := http.NewFakeDownloader()
|
||||||
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/MANIFEST.udebs", "MANIFEST.udebs")
|
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/MANIFEST.udebs", "MANIFEST.udebs")
|
||||||
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/udeb.list", "udeb.list")
|
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/udeb.list", "udeb.list")
|
||||||
@@ -395,7 +395,7 @@ func (s *PackageSuite) TestLinkFromPool(c *C) {
|
|||||||
p := NewPackageFromControlFile(s.stanza)
|
p := NewPackageFromControlFile(s.stanza)
|
||||||
|
|
||||||
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
||||||
c.Assert(ioutil.WriteFile(tmpFilepath, nil, 0777), IsNil)
|
c.Assert(os.WriteFile(tmpFilepath, nil, 0777), IsNil)
|
||||||
|
|
||||||
p.Files()[0].PoolPath, _ = packagePool.Import(tmpFilepath, p.Files()[0].Filename, &p.Files()[0].Checksums, false, cs)
|
p.Files()[0].PoolPath, _ = packagePool.Import(tmpFilepath, p.Files()[0].Filename, &p.Files()[0].Checksums, false, cs)
|
||||||
|
|
||||||
@@ -434,7 +434,7 @@ func (s *PackageSuite) TestDownloadList(c *C) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
||||||
c.Assert(ioutil.WriteFile(tmpFilepath, []byte("abcde"), 0777), IsNil)
|
c.Assert(os.WriteFile(tmpFilepath, []byte("abcde"), 0777), IsNil)
|
||||||
p.Files()[0].PoolPath, _ = packagePool.Import(tmpFilepath, p.Files()[0].Filename, &p.Files()[0].Checksums, false, cs)
|
p.Files()[0].PoolPath, _ = packagePool.Import(tmpFilepath, p.Files()[0].Filename, &p.Files()[0].Checksums, false, cs)
|
||||||
|
|
||||||
list, err = p.DownloadList(packagePool, cs)
|
list, err = p.DownloadList(packagePool, cs)
|
||||||
@@ -449,7 +449,7 @@ func (s *PackageSuite) TestVerifyFiles(c *C) {
|
|||||||
cs := files.NewMockChecksumStorage()
|
cs := files.NewMockChecksumStorage()
|
||||||
|
|
||||||
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
||||||
c.Assert(ioutil.WriteFile(tmpFilepath, []byte("abcde"), 0777), IsNil)
|
c.Assert(os.WriteFile(tmpFilepath, []byte("abcde"), 0777), IsNil)
|
||||||
|
|
||||||
p.Files()[0].PoolPath, _ = packagePool.Import(tmpFilepath, p.Files()[0].Filename, &p.Files()[0].Checksums, false, cs)
|
p.Files()[0].PoolPath, _ = packagePool.Import(tmpFilepath, p.Files()[0].Filename, &p.Files()[0].Checksums, false, cs)
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -28,6 +28,11 @@ func ParsePPA(ppaURL string, config *utils.ConfigStructure) (url string, distrib
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseurl := config.PpaBaseURL
|
||||||
|
if baseurl == "" {
|
||||||
|
baseurl = "http://ppa.launchpad.net"
|
||||||
|
}
|
||||||
|
|
||||||
codename := config.PpaCodename
|
codename := config.PpaCodename
|
||||||
if codename == "" {
|
if codename == "" {
|
||||||
codename, err = getCodename()
|
codename, err = getCodename()
|
||||||
@@ -39,7 +44,7 @@ func ParsePPA(ppaURL string, config *utils.ConfigStructure) (url string, distrib
|
|||||||
|
|
||||||
distribution = codename
|
distribution = codename
|
||||||
components = []string{"main"}
|
components = []string{"main"}
|
||||||
url = fmt.Sprintf("http://ppa.launchpad.net/%s/%s/%s", matches[1], matches[2], distributorID)
|
url = fmt.Sprintf("%s/%s/%s/%s", baseurl, matches[1], matches[2], distributorID)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-12
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ type PublishedRepo struct {
|
|||||||
Label string
|
Label string
|
||||||
Suite string
|
Suite string
|
||||||
Codename string
|
Codename string
|
||||||
|
Version string
|
||||||
// Architectures is a list of all architectures published
|
// Architectures is a list of all architectures published
|
||||||
Architectures []string
|
Architectures []string
|
||||||
// SourceKind is "local"/"repo"
|
// SourceKind is "local"/"repo"
|
||||||
@@ -81,6 +83,11 @@ type PublishedRepo struct {
|
|||||||
// Provide index files per hash also
|
// Provide index files per hash also
|
||||||
AcquireByHash bool
|
AcquireByHash bool
|
||||||
|
|
||||||
|
// An optional field containing a comma separated list
|
||||||
|
// of OpenPGP key fingerprints to be used
|
||||||
|
// for validating the next Release file
|
||||||
|
SignedBy string
|
||||||
|
|
||||||
// Support multiple distributions
|
// Support multiple distributions
|
||||||
MultiDist bool
|
MultiDist bool
|
||||||
|
|
||||||
@@ -520,6 +527,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
|||||||
"Origin": p.Origin,
|
"Origin": p.Origin,
|
||||||
"Suite": p.Suite,
|
"Suite": p.Suite,
|
||||||
"Codename": p.Codename,
|
"Codename": p.Codename,
|
||||||
|
"Version": p.Version,
|
||||||
"NotAutomatic": p.NotAutomatic,
|
"NotAutomatic": p.NotAutomatic,
|
||||||
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
|
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
|
||||||
"Prefix": p.Prefix,
|
"Prefix": p.Prefix,
|
||||||
@@ -529,6 +537,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
|||||||
"Storage": p.Storage,
|
"Storage": p.Storage,
|
||||||
"SkipContents": p.SkipContents,
|
"SkipContents": p.SkipContents,
|
||||||
"AcquireByHash": p.AcquireByHash,
|
"AcquireByHash": p.AcquireByHash,
|
||||||
|
"SignedBy": p.SignedBy,
|
||||||
"MultiDist": p.MultiDist,
|
"MultiDist": p.MultiDist,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -631,7 +640,7 @@ func (p *PublishedRepo) Components() []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Components returns sorted list of published repo source names
|
// SourceNames returns sorted list of published repo source names
|
||||||
func (p *PublishedRepo) SourceNames() []string {
|
func (p *PublishedRepo) SourceNames() []string {
|
||||||
var sources = []string{}
|
var sources = []string{}
|
||||||
|
|
||||||
@@ -702,7 +711,7 @@ func (p *PublishedRepo) Encode() []byte {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||||
encoder.Encode(p)
|
_ = encoder.Encode(p)
|
||||||
|
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
@@ -884,7 +893,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tempDir)
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
||||||
|
|
||||||
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix, p.AcquireByHash, p.SkipBz2)
|
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix, p.AcquireByHash, p.SkipBz2)
|
||||||
|
|
||||||
@@ -970,7 +979,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
contentIndexesMap[key] = contentIndex
|
contentIndexesMap[key] = contentIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
contentIndex.Push(qualifiedName, contents, batch)
|
_ = contentIndex.Push(qualifiedName, contents, batch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,6 +1055,38 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass-through AppStream (DEP-11) files from snapshots
|
||||||
|
for component, item := range p.sourceItems {
|
||||||
|
if item.snapshot == nil || len(item.snapshot.AppStreamFiles) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := component + "/"
|
||||||
|
for relPath, poolPath := range item.snapshot.AppStreamFiles {
|
||||||
|
if !strings.HasPrefix(relPath, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
withinComponent := strings.TrimPrefix(relPath, prefix)
|
||||||
|
|
||||||
|
poolFile, err := packagePool.Open(poolPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to open AppStream file from pool: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bufWriter, err := indexes.SkelIndex(component, withinComponent).BufWriter()
|
||||||
|
if err != nil {
|
||||||
|
_ = poolFile.Close()
|
||||||
|
return fmt.Errorf("unable to generate AppStream index: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = bufio.NewReader(poolFile).WriteTo(bufWriter)
|
||||||
|
_ = poolFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to write AppStream file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
udebs := []bool{false}
|
udebs := []bool{false}
|
||||||
if hadUdebs {
|
if hadUdebs {
|
||||||
udebs = append(udebs, true)
|
udebs = append(udebs, true)
|
||||||
@@ -1070,6 +1111,12 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
if p.AcquireByHash {
|
if p.AcquireByHash {
|
||||||
release["Acquire-By-Hash"] = "yes"
|
release["Acquire-By-Hash"] = "yes"
|
||||||
}
|
}
|
||||||
|
if p.SignedBy != "" {
|
||||||
|
release["Signed-By"] = p.SignedBy
|
||||||
|
}
|
||||||
|
if p.Version != "" {
|
||||||
|
release["Version"] = p.Version
|
||||||
|
}
|
||||||
|
|
||||||
var bufWriter *bufio.Writer
|
var bufWriter *bufio.Writer
|
||||||
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
||||||
@@ -1126,11 +1173,31 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
release["Label"] = p.GetLabel()
|
release["Label"] = p.GetLabel()
|
||||||
release["Suite"] = p.GetSuite()
|
release["Suite"] = p.GetSuite()
|
||||||
release["Codename"] = p.GetCodename()
|
release["Codename"] = p.GetCodename()
|
||||||
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
datetimeformat := "Mon, 2 Jan 2006 15:04:05 MST"
|
||||||
|
|
||||||
|
publishDate := time.Now().UTC()
|
||||||
|
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
|
||||||
|
if sec, err := strconv.ParseInt(epoch, 10, 64); err == nil {
|
||||||
|
publishDate = time.Unix(sec, 0).UTC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release["Date"] = publishDate.Format(datetimeformat)
|
||||||
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
||||||
if p.AcquireByHash {
|
if p.AcquireByHash {
|
||||||
release["Acquire-By-Hash"] = "yes"
|
release["Acquire-By-Hash"] = "yes"
|
||||||
}
|
}
|
||||||
|
if p.SignedBy != "" {
|
||||||
|
// "If the field is present, a client should only accept future updates
|
||||||
|
// to the repository that are signed with keys listed in the field.
|
||||||
|
// The field should be ignored if the Valid-Until field
|
||||||
|
// is not present or if it is expired."
|
||||||
|
release["Signed-By"] = p.SignedBy
|
||||||
|
// Let's use a century as a "forever" value.
|
||||||
|
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetimeformat)
|
||||||
|
}
|
||||||
|
if p.Version != "" {
|
||||||
|
release["Version"] = p.Version
|
||||||
|
}
|
||||||
release["Description"] = " Generated by aptly\n"
|
release["Description"] = " Generated by aptly\n"
|
||||||
release["MD5Sum"] = ""
|
release["MD5Sum"] = ""
|
||||||
release["SHA1"] = ""
|
release["SHA1"] = ""
|
||||||
@@ -1275,11 +1342,11 @@ func (collection *PublishedRepoCollection) CheckDuplicate(repo *PublishedRepo) *
|
|||||||
// Update stores updated information about repo in DB
|
// Update stores updated information about repo in DB
|
||||||
func (collection *PublishedRepoCollection) Update(repo *PublishedRepo) error {
|
func (collection *PublishedRepoCollection) Update(repo *PublishedRepo) error {
|
||||||
batch := collection.db.CreateBatch()
|
batch := collection.db.CreateBatch()
|
||||||
batch.Put(repo.Key(), repo.Encode())
|
_ = batch.Put(repo.Key(), repo.Encode())
|
||||||
|
|
||||||
if repo.SourceKind == SourceLocalRepo {
|
if repo.SourceKind == SourceLocalRepo {
|
||||||
for component, item := range repo.sourceItems {
|
for component, item := range repo.sourceItems {
|
||||||
batch.Put(repo.RefKey(component), item.packageRefs.Encode())
|
_ = batch.Put(repo.RefKey(component), item.packageRefs.Encode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return batch.Write()
|
return batch.Write()
|
||||||
@@ -1324,7 +1391,7 @@ func (collection *PublishedRepoCollection) LoadShallow(repo *PublishedRepo, coll
|
|||||||
|
|
||||||
// LoadComplete loads complete information on the sources of the repo *and* their packages
|
// LoadComplete loads complete information on the sources of the repo *and* their packages
|
||||||
func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) {
|
func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) {
|
||||||
collection.LoadShallow(repo, collectionFactory)
|
_ = collection.LoadShallow(repo, collectionFactory)
|
||||||
|
|
||||||
if repo.SourceKind == SourceSnapshot {
|
if repo.SourceKind == SourceSnapshot {
|
||||||
for _, item := range repo.sourceItems {
|
for _, item := range repo.sourceItems {
|
||||||
@@ -1502,7 +1569,7 @@ func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
packageList.ForEach(func(p *Package) error {
|
_ = packageList.ForEach(func(p *Package) error {
|
||||||
poolDir, err := p.PoolDirectory()
|
poolDir, err := p.PoolDirectory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1575,7 +1642,7 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(published
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
packageList.ForEach(func(p *Package) error {
|
_ = packageList.ForEach(func(p *Package) error {
|
||||||
poolDir, err := p.PoolDirectory()
|
poolDir, err := p.PoolDirectory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1709,10 +1776,10 @@ func (collection *PublishedRepoCollection) Remove(publishedStorageProvider aptly
|
|||||||
}
|
}
|
||||||
|
|
||||||
batch := collection.db.CreateBatch()
|
batch := collection.db.CreateBatch()
|
||||||
batch.Delete(repo.Key())
|
_ = batch.Delete(repo.Key())
|
||||||
|
|
||||||
for _, component := range repo.Components() {
|
for _, component := range repo.Components() {
|
||||||
batch.Delete(repo.RefKey(component))
|
_ = batch.Delete(repo.RefKey(component))
|
||||||
}
|
}
|
||||||
|
|
||||||
return batch.Write()
|
return batch.Write()
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ func BenchmarkListReferencedFiles(b *testing.B) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer func() { _ = os.RemoveAll(tmpDir) }()
|
||||||
|
|
||||||
db, err := goleveldb.NewOpenDB(tmpDir)
|
db, err := goleveldb.NewOpenDB(tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer func() { _ = db.Close() }()
|
||||||
|
|
||||||
factory := NewCollectionFactory(db)
|
factory := NewCollectionFactory(db)
|
||||||
packageCollection := factory.PackageCollection()
|
packageCollection := factory.PackageCollection()
|
||||||
@@ -49,7 +49,7 @@ func BenchmarkListReferencedFiles(b *testing.B) {
|
|||||||
Filename: fmt.Sprintf("pkg-shared_%d.deb", pkgIndex),
|
Filename: fmt.Sprintf("pkg-shared_%d.deb", pkgIndex),
|
||||||
}})
|
}})
|
||||||
|
|
||||||
packageCollection.UpdateInTransaction(p, transaction)
|
_ = packageCollection.UpdateInTransaction(p, transaction)
|
||||||
sharedRefs.Refs = append(sharedRefs.Refs, p.Key(""))
|
sharedRefs.Refs = append(sharedRefs.Refs, p.Key(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ func BenchmarkListReferencedFiles(b *testing.B) {
|
|||||||
Filename: fmt.Sprintf("pkg%d_%d.deb", repoIndex, pkgIndex),
|
Filename: fmt.Sprintf("pkg%d_%d.deb", repoIndex, pkgIndex),
|
||||||
}})
|
}})
|
||||||
|
|
||||||
packageCollection.UpdateInTransaction(p, transaction)
|
_ = packageCollection.UpdateInTransaction(p, transaction)
|
||||||
refs.Refs = append(refs.Refs, p.Key(""))
|
refs.Refs = append(refs.Refs, p.Key(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,16 +92,16 @@ func BenchmarkListReferencedFiles(b *testing.B) {
|
|||||||
repo.DefaultDistribution = fmt.Sprintf("dist%d", repoIndex)
|
repo.DefaultDistribution = fmt.Sprintf("dist%d", repoIndex)
|
||||||
repo.DefaultComponent = defaultComponent
|
repo.DefaultComponent = defaultComponent
|
||||||
repo.UpdateRefList(refs.Merge(sharedRefs, false, true))
|
repo.UpdateRefList(refs.Merge(sharedRefs, false, true))
|
||||||
repoCollection.Add(repo)
|
_ = repoCollection.Add(repo)
|
||||||
|
|
||||||
publish, err := NewPublishedRepo("", "test", "", nil, []string{defaultComponent}, []interface{}{repo}, factory, false)
|
publish, err := NewPublishedRepo("", "test", "", nil, []string{defaultComponent}, []interface{}{repo}, factory, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
publishCollection.Add(publish)
|
_ = publishCollection.Add(publish)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.CompactDB()
|
_ = db.CompactDB()
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
+163
-47
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
@@ -51,11 +51,11 @@ func (n *NullSigner) SetPassphrase(passphrase, passphraseFile string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *NullSigner) DetachedSign(source string, destination string) error {
|
func (n *NullSigner) DetachedSign(source string, destination string) error {
|
||||||
return ioutil.WriteFile(destination, []byte{}, 0644)
|
return os.WriteFile(destination, []byte{}, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NullSigner) ClearSign(source string, destination string) error {
|
func (n *NullSigner) ClearSign(source string, destination string) error {
|
||||||
return ioutil.WriteFile(destination, []byte{}, 0644)
|
return os.WriteFile(destination, []byte{}, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeStorageProvider struct {
|
type FakeStorageProvider struct {
|
||||||
@@ -104,7 +104,7 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
|||||||
s.cs = files.NewMockChecksumStorage()
|
s.cs = files.NewMockChecksumStorage()
|
||||||
|
|
||||||
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
tmpFilepath := filepath.Join(c.MkDir(), "file")
|
||||||
c.Assert(ioutil.WriteFile(tmpFilepath, nil, 0777), IsNil)
|
c.Assert(os.WriteFile(tmpFilepath, nil, 0777), IsNil)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s.p1.Files()[0].PoolPath, err = s.packagePool.Import(tmpFilepath, s.p1.Files()[0].Filename, &s.p1.Files()[0].Checksums, false, s.cs)
|
s.p1.Files()[0].PoolPath, err = s.packagePool.Import(tmpFilepath, s.p1.Files()[0].Filename, &s.p1.Files()[0].Checksums, false, s.cs)
|
||||||
@@ -116,24 +116,24 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.reflist = NewPackageRefListFromPackageList(s.list)
|
s.reflist = NewPackageRefListFromPackageList(s.list)
|
||||||
|
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
||||||
repo.packageRefs = s.reflist
|
repo.packageRefs = s.reflist
|
||||||
s.factory.RemoteRepoCollection().Add(repo)
|
_ = s.factory.RemoteRepoCollection().Add(repo)
|
||||||
|
|
||||||
s.localRepo = NewLocalRepo("local1", "comment1")
|
s.localRepo = NewLocalRepo("local1", "comment1")
|
||||||
s.localRepo.packageRefs = s.reflist
|
s.localRepo.packageRefs = s.reflist
|
||||||
s.factory.LocalRepoCollection().Add(s.localRepo)
|
_ = s.factory.LocalRepoCollection().Add(s.localRepo)
|
||||||
|
|
||||||
s.snapshot, _ = NewSnapshotFromRepository("snap", repo)
|
s.snapshot, _ = NewSnapshotFromRepository("snap", repo)
|
||||||
s.factory.SnapshotCollection().Add(s.snapshot)
|
_ = s.factory.SnapshotCollection().Add(s.snapshot)
|
||||||
|
|
||||||
s.snapshot2, _ = NewSnapshotFromRepository("snap", repo)
|
s.snapshot2, _ = NewSnapshotFromRepository("snap", repo)
|
||||||
s.factory.SnapshotCollection().Add(s.snapshot2)
|
_ = s.factory.SnapshotCollection().Add(s.snapshot2)
|
||||||
|
|
||||||
s.packageCollection = s.factory.PackageCollection()
|
s.packageCollection = s.factory.PackageCollection()
|
||||||
s.packageCollection.Update(s.p1)
|
_ = s.packageCollection.Update(s.p1)
|
||||||
s.packageCollection.Update(s.p2)
|
_ = s.packageCollection.Update(s.p2)
|
||||||
s.packageCollection.Update(s.p3)
|
_ = s.packageCollection.Update(s.p3)
|
||||||
|
|
||||||
s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
s.repo.SkipContents = true
|
s.repo.SkipContents = true
|
||||||
@@ -152,7 +152,7 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TearDownTest(c *C) {
|
func (s *PublishedRepoSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) {
|
func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) {
|
||||||
@@ -179,12 +179,12 @@ func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) {
|
|||||||
c.Check(s.repo3.RefList("main").Len(), Equals, 3)
|
c.Check(s.repo3.RefList("main").Len(), Equals, 3)
|
||||||
c.Check(s.repo3.RefList("contrib").Len(), Equals, 3)
|
c.Check(s.repo3.RefList("contrib").Len(), Equals, 3)
|
||||||
|
|
||||||
c.Check(func() { NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory, false) }, PanicMatches, "publish with empty sources")
|
c.Check(func() { _, _ = NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory, false) }, PanicMatches, "publish with empty sources")
|
||||||
c.Check(func() {
|
c.Check(func() {
|
||||||
NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
_, _ = NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
}, PanicMatches, "sources and components should be equal in size")
|
}, PanicMatches, "sources and components should be equal in size")
|
||||||
c.Check(func() {
|
c.Check(func() {
|
||||||
NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory, false)
|
_, _ = NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory, false)
|
||||||
}, PanicMatches, "interface conversion:.*")
|
}, PanicMatches, "interface conversion:.*")
|
||||||
|
|
||||||
_, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
_, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
@@ -337,7 +337,7 @@ func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) {
|
|||||||
|
|
||||||
s.localRepo.DefaultDistribution = "precise"
|
s.localRepo.DefaultDistribution = "precise"
|
||||||
s.localRepo.DefaultComponent = "contrib"
|
s.localRepo.DefaultComponent = "contrib"
|
||||||
s.factory.LocalRepoCollection().Update(s.localRepo)
|
_ = s.factory.LocalRepoCollection().Update(s.localRepo)
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false)
|
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
@@ -426,6 +426,81 @@ func (s *PublishedRepoSuite) TestPublish(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublishedRepoSuite) TestPublishAppStream(c *C) {
|
||||||
|
// Components + icons
|
||||||
|
content1 := []byte("DEP-11 test content for Components-amd64.yml.gz")
|
||||||
|
tmpFile1 := filepath.Join(c.MkDir(), "Components-amd64.yml.gz")
|
||||||
|
c.Assert(os.WriteFile(tmpFile1, content1, 0644), IsNil)
|
||||||
|
|
||||||
|
checksums1 := utils.ChecksumInfo{Size: int64(len(content1))}
|
||||||
|
poolPath1, err := s.packagePool.Import(tmpFile1, "Components-amd64.yml.gz", &checksums1, false, s.cs)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
content2 := []byte("DEP-11 icons tar data")
|
||||||
|
tmpFile2 := filepath.Join(c.MkDir(), "icons-48x48.tar.gz")
|
||||||
|
c.Assert(os.WriteFile(tmpFile2, content2, 0644), IsNil)
|
||||||
|
|
||||||
|
checksums2 := utils.ChecksumInfo{Size: int64(len(content2))}
|
||||||
|
poolPath2, err := s.packagePool.Import(tmpFile2, "icons-48x48.tar.gz", &checksums2, false, s.cs)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
// Include contrib file that should be skipped
|
||||||
|
contribContent := []byte("DEP-11 contrib content")
|
||||||
|
tmpFile3 := filepath.Join(c.MkDir(), "Components-contrib.yml.gz")
|
||||||
|
c.Assert(os.WriteFile(tmpFile3, contribContent, 0644), IsNil)
|
||||||
|
|
||||||
|
checksums3 := utils.ChecksumInfo{Size: int64(len(contribContent))}
|
||||||
|
poolPath3, err := s.packagePool.Import(tmpFile3, "Components-contrib.yml.gz", &checksums3, false, s.cs)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
s.snapshot.AppStreamFiles = map[string]string{
|
||||||
|
"main/dep11/Components-amd64.yml.gz": poolPath1,
|
||||||
|
"main/dep11/icons-48x48.tar.gz": poolPath2,
|
||||||
|
"contrib/dep11/Components-amd64.yml.gz": poolPath3,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
// Both main files should exist
|
||||||
|
appstreamPath1 := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/dep11/Components-amd64.yml.gz")
|
||||||
|
c.Check(appstreamPath1, PathExists)
|
||||||
|
actual1, err := os.ReadFile(appstreamPath1)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(actual1, DeepEquals, content1)
|
||||||
|
|
||||||
|
appstreamPath2 := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/dep11/icons-48x48.tar.gz")
|
||||||
|
c.Check(appstreamPath2, PathExists)
|
||||||
|
actual2, err := os.ReadFile(appstreamPath2)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(actual2, DeepEquals, content2)
|
||||||
|
|
||||||
|
// Contrib file should not appear
|
||||||
|
contribPath := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/contrib/dep11/Components-amd64.yml.gz")
|
||||||
|
_, statErr := os.Stat(contribPath)
|
||||||
|
c.Check(os.IsNotExist(statErr), Equals, true)
|
||||||
|
|
||||||
|
// Release file should reference AppStream files
|
||||||
|
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer func() { _ = rf.Close() }()
|
||||||
|
|
||||||
|
cfr := NewControlFileReader(rf, true, false)
|
||||||
|
st, err := cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Check(st["MD5Sum"], Matches, "(?s).*main/dep11/Components-amd64\\.yml\\.gz.*")
|
||||||
|
c.Check(st["SHA256"], Matches, "(?s).*main/dep11/Components-amd64\\.yml\\.gz.*")
|
||||||
|
|
||||||
|
// Pool open error
|
||||||
|
s.snapshot.AppStreamFiles = map[string]string{
|
||||||
|
"main/dep11/Components-amd64.yml.gz": "nonexistent/pool/path",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||||
|
c.Assert(err, ErrorMatches, "unable to open AppStream file from pool.*")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestPublishNoSigner(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.Assert(err, IsNil)
|
||||||
@@ -434,6 +509,47 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
|||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"), PathExists)
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"), PathExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublishedRepoSuite) TestPublishSourceDateEpoch(c *C) {
|
||||||
|
// Test with SOURCE_DATE_EPOCH set
|
||||||
|
_ = os.Setenv("SOURCE_DATE_EPOCH", "1234567890")
|
||||||
|
defer func() { _ = os.Unsetenv("SOURCE_DATE_EPOCH") }()
|
||||||
|
|
||||||
|
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer func() { _ = rf.Close() }()
|
||||||
|
|
||||||
|
cfr := NewControlFileReader(rf, true, false)
|
||||||
|
st, err := cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
// Expected date for Unix timestamp 1234567890: Fri, 13 Feb 2009 23:31:30 UTC
|
||||||
|
c.Check(st["Date"], Equals, "Fri, 13 Feb 2009 23:31:30 UTC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedRepoSuite) TestPublishSourceDateEpochInvalid(c *C) {
|
||||||
|
// Test with invalid SOURCE_DATE_EPOCH (should fallback to current time)
|
||||||
|
_ = os.Setenv("SOURCE_DATE_EPOCH", "invalid")
|
||||||
|
defer func() { _ = os.Unsetenv("SOURCE_DATE_EPOCH") }()
|
||||||
|
|
||||||
|
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer func() { _ = rf.Close() }()
|
||||||
|
|
||||||
|
cfr := NewControlFileReader(rf, true, false)
|
||||||
|
st, err := cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
// Should have a valid Date field (not empty, not the fixed date from SOURCE_DATE_EPOCH)
|
||||||
|
c.Check(st["Date"], Not(Equals), "")
|
||||||
|
c.Check(st["Date"], Not(Equals), "Fri, 13 Feb 2009 23:31:30 UTC")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestPublishLocalRepo(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.Assert(err, IsNil)
|
||||||
@@ -526,8 +642,8 @@ func (s *PublishedRepoSuite) TestPublishedRepoRevision(c *C) {
|
|||||||
bytes, err := json.Marshal(revision)
|
bytes, err := json.Marshal(revision)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
json_expected := `{"Sources":[{"Component":"main","Name":"local1"},{"Component":"test1","Name":"snap1"},{"Component":"test2","Name":"snap2"}]}`
|
jsonExpected := `{"Sources":[{"Component":"main","Name":"local1"},{"Component":"test1","Name":"snap1"},{"Component":"test2","Name":"snap2"}]}`
|
||||||
c.Assert(string(bytes), Equals, json_expected)
|
c.Assert(string(bytes), Equals, jsonExpected)
|
||||||
|
|
||||||
c.Assert(s.repo2.DropRevision(), DeepEquals, revision)
|
c.Assert(s.repo2.DropRevision(), DeepEquals, revision)
|
||||||
c.Assert(s.repo2.Revision, IsNil)
|
c.Assert(s.repo2.Revision, IsNil)
|
||||||
@@ -564,11 +680,11 @@ func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
|
|||||||
sort.Sort(snap2Refs)
|
sort.Sort(snap2Refs)
|
||||||
s.snap2 = NewSnapshotFromRefList("snap2", []*Snapshot{}, snap2Refs, "desc2")
|
s.snap2 = NewSnapshotFromRefList("snap2", []*Snapshot{}, snap2Refs, "desc2")
|
||||||
|
|
||||||
s.snapshotCollection.Add(s.snap1)
|
_ = s.snapshotCollection.Add(s.snap1)
|
||||||
s.snapshotCollection.Add(s.snap2)
|
_ = s.snapshotCollection.Add(s.snap2)
|
||||||
|
|
||||||
s.localRepo = NewLocalRepo("local1", "comment1")
|
s.localRepo = NewLocalRepo("local1", "comment1")
|
||||||
s.factory.LocalRepoCollection().Add(s.localRepo)
|
_ = s.factory.LocalRepoCollection().Add(s.localRepo)
|
||||||
|
|
||||||
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
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.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main", "contrib"}, []interface{}{s.snap2, s.snap1}, s.factory, false)
|
||||||
@@ -580,7 +696,7 @@ func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoCollectionSuite) TearDownTest(c *C) {
|
func (s *PublishedRepoCollectionSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoCollectionSuite) TestAddByStoragePrefixDistribution(c *C) {
|
func (s *PublishedRepoCollectionSuite) TestAddByStoragePrefixDistribution(c *C) {
|
||||||
@@ -677,7 +793,7 @@ func (s *PublishedRepoCollectionSuite) TestLoadPre0_6(c *C) {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||||
encoder.Encode(&old)
|
_ = encoder.Encode(&old)
|
||||||
|
|
||||||
c.Assert(s.db.Put(s.repo1.Key(), buf.Bytes()), IsNil)
|
c.Assert(s.db.Put(s.repo1.Key(), buf.Bytes()), IsNil)
|
||||||
c.Assert(s.db.Put(s.repo1.RefKey(""), s.localRepo.RefList().Encode()), IsNil)
|
c.Assert(s.db.Put(s.repo1.RefKey(""), s.localRepo.RefList().Encode()), IsNil)
|
||||||
@@ -695,7 +811,7 @@ func (s *PublishedRepoCollectionSuite) TestLoadPre0_6(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoCollectionSuite) TestForEachAndLen(c *C) {
|
func (s *PublishedRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||||
s.collection.Add(s.repo1)
|
_ = s.collection.Add(s.repo1)
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
err := s.collection.ForEach(func(*PublishedRepo) error {
|
err := s.collection.ForEach(func(*PublishedRepo) error {
|
||||||
@@ -755,7 +871,7 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
|
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
|
||||||
s.snapshotCollection.Add(snap3)
|
_ = s.snapshotCollection.Add(snap3)
|
||||||
|
|
||||||
// Ensure that adding a second publish point with matching files doesn't give duplicate results.
|
// 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)
|
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false)
|
||||||
@@ -799,7 +915,7 @@ func (s *PublishedRepoRemoveSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1")
|
s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1")
|
||||||
|
|
||||||
s.snapshotCollection.Add(s.snap1)
|
_ = s.snapshotCollection.Add(s.snap1)
|
||||||
|
|
||||||
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
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.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
@@ -808,26 +924,26 @@ func (s *PublishedRepoRemoveSuite) SetUpTest(c *C) {
|
|||||||
s.repo5, _ = NewPublishedRepo("files:other", "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 = s.factory.PublishedRepoCollection()
|
||||||
s.collection.Add(s.repo1)
|
_ = s.collection.Add(s.repo1)
|
||||||
s.collection.Add(s.repo2)
|
_ = s.collection.Add(s.repo2)
|
||||||
s.collection.Add(s.repo3)
|
_ = s.collection.Add(s.repo3)
|
||||||
s.collection.Add(s.repo4)
|
_ = s.collection.Add(s.repo4)
|
||||||
s.collection.Add(s.repo5)
|
_ = s.collection.Add(s.repo5)
|
||||||
|
|
||||||
s.root = c.MkDir()
|
s.root = c.MkDir()
|
||||||
s.publishedStorage = files.NewPublishedStorage(s.root, "", "")
|
s.publishedStorage = files.NewPublishedStorage(s.root, "", "")
|
||||||
s.publishedStorage.MkDir("ppa/dists/anaconda")
|
_ = s.publishedStorage.MkDir("ppa/dists/anaconda")
|
||||||
s.publishedStorage.MkDir("ppa/dists/meduza")
|
_ = s.publishedStorage.MkDir("ppa/dists/meduza")
|
||||||
s.publishedStorage.MkDir("ppa/dists/osminog")
|
_ = s.publishedStorage.MkDir("ppa/dists/osminog")
|
||||||
s.publishedStorage.MkDir("ppa/pool/main")
|
_ = s.publishedStorage.MkDir("ppa/pool/main")
|
||||||
s.publishedStorage.MkDir("ppa/pool/contrib")
|
_ = s.publishedStorage.MkDir("ppa/pool/contrib")
|
||||||
s.publishedStorage.MkDir("dists/anaconda")
|
_ = s.publishedStorage.MkDir("dists/anaconda")
|
||||||
s.publishedStorage.MkDir("pool/main")
|
_ = s.publishedStorage.MkDir("pool/main")
|
||||||
|
|
||||||
s.root2 = c.MkDir()
|
s.root2 = c.MkDir()
|
||||||
s.publishedStorage2 = files.NewPublishedStorage(s.root2, "", "")
|
s.publishedStorage2 = files.NewPublishedStorage(s.root2, "", "")
|
||||||
s.publishedStorage2.MkDir("ppa/dists/osminog")
|
_ = s.publishedStorage2.MkDir("ppa/dists/osminog")
|
||||||
s.publishedStorage2.MkDir("ppa/pool/contrib")
|
_ = s.publishedStorage2.MkDir("ppa/pool/contrib")
|
||||||
|
|
||||||
s.provider = &FakeStorageProvider{map[string]aptly.PublishedStorage{
|
s.provider = &FakeStorageProvider{map[string]aptly.PublishedStorage{
|
||||||
"": s.publishedStorage,
|
"": s.publishedStorage,
|
||||||
@@ -835,11 +951,11 @@ func (s *PublishedRepoRemoveSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoRemoveSuite) TearDownTest(c *C) {
|
func (s *PublishedRepoRemoveSuite) TearDownTest(c *C) {
|
||||||
s.db.Close()
|
_ = s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesOnlyDist(c *C) {
|
func (s *PublishedRepoRemoveSuite) TestRemoveFilesOnlyDist(c *C) {
|
||||||
s.repo1.RemoveFiles(s.provider, false, []string{}, nil)
|
_ = s.repo1.RemoveFiles(s.provider, false, []string{}, nil)
|
||||||
|
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||||
@@ -853,7 +969,7 @@ func (s *PublishedRepoRemoveSuite) TestRemoveFilesOnlyDist(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPool(c *C) {
|
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPool(c *C) {
|
||||||
s.repo1.RemoveFiles(s.provider, false, []string{"main"}, nil)
|
_ = s.repo1.RemoveFiles(s.provider, false, []string{"main"}, nil)
|
||||||
|
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||||
@@ -867,7 +983,7 @@ func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPool(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithTwoPools(c *C) {
|
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithTwoPools(c *C) {
|
||||||
s.repo1.RemoveFiles(s.provider, false, []string{"main", "contrib"}, nil)
|
_ = s.repo1.RemoveFiles(s.provider, false, []string{"main", "contrib"}, nil)
|
||||||
|
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||||
@@ -881,7 +997,7 @@ func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithTwoPools(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefix(c *C) {
|
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefix(c *C) {
|
||||||
s.repo1.RemoveFiles(s.provider, true, []string{"main"}, nil)
|
_ = s.repo1.RemoveFiles(s.provider, true, []string{"main"}, nil)
|
||||||
|
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), Not(PathExists))
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), Not(PathExists))
|
||||||
@@ -895,7 +1011,7 @@ func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefix(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefixRoot(c *C) {
|
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefixRoot(c *C) {
|
||||||
s.repo2.RemoveFiles(s.provider, true, []string{"main"}, nil)
|
_ = s.repo2.RemoveFiles(s.provider, true, []string{"main"}, nil)
|
||||||
|
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), PathExists)
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), PathExists)
|
||||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||||
|
|||||||
+2
-2
@@ -89,7 +89,7 @@ func (q *OrQuery) Fast(list PackageCatalog) bool {
|
|||||||
func (q *OrQuery) Query(list PackageCatalog) (result *PackageList) {
|
func (q *OrQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||||
if q.Fast(list) {
|
if q.Fast(list) {
|
||||||
result = q.L.Query(list)
|
result = q.L.Query(list)
|
||||||
result.Append(q.R.Query(list))
|
_ = result.Append(q.R.Query(list))
|
||||||
} else {
|
} else {
|
||||||
result = list.Scan(q)
|
result = list.Scan(q)
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ func (q *DependencyQuery) Query(list PackageCatalog) (result *PackageList) {
|
|||||||
if q.Fast(list) {
|
if q.Fast(list) {
|
||||||
result = NewPackageList()
|
result = NewPackageList()
|
||||||
for _, pkg := range list.Search(q.Dep, true, true) {
|
for _, pkg := range list.Search(q.Dep, true, true) {
|
||||||
result.Add(pkg)
|
_ = result.Add(pkg)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = list.Scan(q)
|
result = list.Scan(q)
|
||||||
|
|||||||
+5
-2
@@ -54,7 +54,7 @@ func (l *PackageRefList) Swap(i, j int) {
|
|||||||
l.Refs[i], l.Refs[j] = l.Refs[j], l.Refs[i]
|
l.Refs[i], l.Refs[j] = l.Refs[j], l.Refs[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare compares two refs in lexographical order
|
// Less compares two refs in lexographical order
|
||||||
func (l *PackageRefList) Less(i, j int) bool {
|
func (l *PackageRefList) Less(i, j int) bool {
|
||||||
return bytes.Compare(l.Refs[i], l.Refs[j]) < 0
|
return bytes.Compare(l.Refs[i], l.Refs[j]) < 0
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ func (l *PackageRefList) Encode() []byte {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||||
encoder.Encode(l)
|
_ = encoder.Encode(l)
|
||||||
|
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
@@ -79,6 +79,9 @@ func (l *PackageRefList) Decode(input []byte) error {
|
|||||||
|
|
||||||
// ForEach calls handler for each package ref in list
|
// ForEach calls handler for each package ref in list
|
||||||
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
||||||
|
if l == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
for _, p := range l.Refs {
|
for _, p := range l.Refs {
|
||||||
err = handler(p)
|
err = handler(p)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user