mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-15 07:00:52 +00:00
Compare commits
376 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d78c467b7 | |||
| 909c5bb116 | |||
| 06bcce22d4 | |||
| b07665da50 | |||
| 034131c7a9 | |||
| c9b1d723c3 | |||
| 9346901001 | |||
| 6416440ed3 | |||
| cb3c0519d6 | |||
| be7c232204 | |||
| 5ff470fd59 | |||
| 8f76f4c24d | |||
| 57c93177ad | |||
| 2a7a04ee32 | |||
| 7ccb256841 | |||
| 00e17d82fe | |||
| 867310e8f1 | |||
| fbac933c30 | |||
| 1ab61457a5 | |||
| 9b1a0ec2da | |||
| 5cfc5a6b8e | |||
| 4655d7c188 | |||
| 88ee47045f | |||
| 84c8e5cf22 | |||
| 813d9c660d | |||
| 057fcaa598 | |||
| 0b6af54eee | |||
| 66e6d3ac6f | |||
| 0aebd14f13 | |||
| b909bccfcd | |||
| 62c92e4256 | |||
| 2f0b2cf4de | |||
| 79bd81e937 | |||
| b1cb14e921 | |||
| 091b6f9948 | |||
| 213fbccead | |||
| f06e428caf | |||
| 8b521fc722 | |||
| 7895edd100 | |||
| 5e3460ae62 | |||
| 58480d747f | |||
| 7716d4236d | |||
| 6a8723484d | |||
| ec4503c941 | |||
| bd95012687 | |||
| d4da3d5440 | |||
| dff6bbb165 | |||
| c9bae4c454 | |||
| 95ef905ca9 | |||
| 6a5f494a1f | |||
| 009087e58f | |||
| c73aa0e255 | |||
| 90e7008b8d | |||
| d7c51530f5 | |||
| c55c0f6e3c | |||
| d9099b7e6b | |||
| 444c7f8af1 | |||
| 9a1a401248 | |||
| 56f5254aa9 | |||
| 4f1838bb74 | |||
| 37841fb205 | |||
| 0164827907 | |||
| fa8e8ab6fb | |||
| 2a87554581 | |||
| 298e09e0b9 | |||
| 4ecbaf5a62 | |||
| 562820b625 | |||
| 2d86506183 | |||
| ef75ff8600 | |||
| 62b324eb65 | |||
| 4c40f4dc0a | |||
| cfdb720ef4 | |||
| 5de38a987a | |||
| c62670ea51 | |||
| 9d0b3a186e | |||
| a5702371ef | |||
| 02227d7233 | |||
| 877ffbe376 | |||
| 37b7c5aa91 | |||
| 3e7978180e | |||
| 4360fb00d7 | |||
| d90825f4f0 | |||
| 5be757e35e | |||
| e51c1894bf | |||
| 24fcde56b6 | |||
| 7390e19e03 | |||
| 0aa0c0a995 | |||
| 1d10dd6ce7 | |||
| e28fa416ab | |||
| d6c7b1d770 | |||
| 92ea4a2505 | |||
| 3e5e0fc119 | |||
| 9fa4248e3b | |||
| d958a146f7 | |||
| 125a7c2c07 | |||
| d403150d77 | |||
| 4e6c52ec2a | |||
| 90ffa6883a | |||
| 4a85be68a0 | |||
| 19e4040b17 | |||
| 767323fde9 | |||
| 5be8231598 | |||
| e9f1947156 | |||
| 3d8c906c7f | |||
| f1c0205e21 | |||
| 7d124ac5c0 | |||
| abebdb94a5 | |||
| 7b45c4d380 | |||
| 6cbd80566f | |||
| 274bb2732a | |||
| 5935c7d5a0 | |||
| 4ab07fef23 | |||
| e7abc4d39a | |||
| 71b045366d | |||
| 7abac9537f | |||
| 9d64dc2fd9 | |||
| 6e1b49daa8 | |||
| 7bbfe88008 | |||
| 4c5db7d98c | |||
| cab280ebc0 | |||
| 2f78cf5129 | |||
| bc3755dcf7 | |||
| a5730feb9d | |||
| b8f084b1dc | |||
| 9fc50e347b | |||
| 034ab23ff1 | |||
| 865a0c92eb | |||
| b3a4eb8897 | |||
| 33536bd69f | |||
| 10bed44763 | |||
| 0111596d50 | |||
| 1df6ce531c | |||
| 1ad63d1786 | |||
| 895cf5e5c6 | |||
| 9b53deb97f | |||
| 6e9e9a6e31 | |||
| 90343b21d3 | |||
| 2369a2dadf | |||
| 237f43f8ba | |||
| 7fcac4ed49 | |||
| d52f325d99 | |||
| 921dfaddb9 | |||
| f642f3fde4 | |||
| f401cea76a | |||
| 76d3b27842 | |||
| 35ad56ff7f | |||
| 3bfc305df8 | |||
| 5ab866f0db | |||
| 12855db1a0 | |||
| e094d79b85 | |||
| 96394ecf38 | |||
| e9600f9d66 | |||
| 94ec8c4548 | |||
| bde3dcc5f5 | |||
| df10066c16 | |||
| 6b52a72359 | |||
| 608e0d8610 | |||
| 94c1b2b755 | |||
| c31ab7b43f | |||
| 0bbf61df95 | |||
| 1b58b88b02 | |||
| 61ef1fe798 | |||
| 863ec4dae9 | |||
| 5aa5b8d9cb | |||
| 37750cefda | |||
| 7be60cd8be | |||
| 606b701b00 | |||
| fe2f17d38a | |||
| 4aeba31a6a | |||
| 2086d424bd | |||
| 520eeea355 | |||
| dad2527182 | |||
| 9a3922fe17 | |||
| 9c2e95d614 | |||
| baba1165ff | |||
| a0610292a7 | |||
| de7f169043 | |||
| 97b7143f6d | |||
| 520b50e49b | |||
| 70cbc12ac7 | |||
| 9a01c64f68 | |||
| 673da76e55 | |||
| 146daa22a7 | |||
| 9150a75886 | |||
| 92bff40eb4 | |||
| 3fd90c74de | |||
| 5039f76fe8 | |||
| 15e14b2a93 | |||
| d41157bd54 | |||
| cfe853e791 | |||
| 4fa420699b | |||
| 2b9a7914fd | |||
| 3bf957c313 | |||
| 25dfd98672 | |||
| a1fd350573 | |||
| bbf5db745f | |||
| 884d695273 | |||
| 65a984ec2b | |||
| e06ecf5092 | |||
| 3bbd61d75b | |||
| 69f851124c | |||
| 5fef06100c | |||
| 40ba4ce958 | |||
| a8aeaff2a3 | |||
| d8fea9f142 | |||
| f33a9dccf8 | |||
| 5c4f97f88e | |||
| 4c7796ca56 | |||
| e6e102a95c | |||
| fee581c722 | |||
| f398ffb183 | |||
| 60e578bad9 | |||
| 55fc2f4d0c | |||
| dc74239275 | |||
| 75ad96e9ca | |||
| 265d1373a1 | |||
| 814ee037d3 | |||
| 4ab22a5fc5 | |||
| 6222250104 | |||
| f2b328395d | |||
| f3732b1683 | |||
| 82ddc7f9ce | |||
| 5031adcb7f | |||
| c5322ff2f6 | |||
| a1f1cf307b | |||
| 1b0cad0f1b | |||
| 29c2603d61 | |||
| ea9657dae0 | |||
| b61875fa9c | |||
| 1308c2f774 | |||
| 63bc8282a0 | |||
| cd0626e825 | |||
| dc4b4a86a4 | |||
| bb6df8ee63 | |||
| 199b5ab9b8 | |||
| 5719d6fcdd | |||
| 29e4ea6ec0 | |||
| 491c8ebdd1 | |||
| 3afb6e47bf | |||
| f767136371 | |||
| 86162f0ef5 | |||
| f03f8378b9 | |||
| 074b35755d | |||
| a92de0d9bd | |||
| f966258772 | |||
| 7938eebdcd | |||
| dccf1acb78 | |||
| 20278a7b5d | |||
| d4268fd4c6 | |||
| e7af54999f | |||
| 916d5a22c2 | |||
| abdd341369 | |||
| a802160318 | |||
| a5c7bde1a3 | |||
| 217a8a8e92 | |||
| e85459c529 | |||
| 2edaf38386 | |||
| cc7f75370f | |||
| 829d9924c3 | |||
| 164cefe2a2 | |||
| 9ac2e25739 | |||
| 1de4d69922 | |||
| dbc5ba9458 | |||
| 1459919984 | |||
| 11b9382e56 | |||
| c49c3cac30 | |||
| 165c79394b | |||
| 8fa7bc9206 | |||
| 842cbc0e44 | |||
| 014f4c49d1 | |||
| c9f52ab9f4 | |||
| c44eb676b0 | |||
| d29fbb8acf | |||
| 1566f9a229 | |||
| b65434650c | |||
| 4ba3e0b941 | |||
| 535d7149bd | |||
| e8df80555c | |||
| d978ae5a15 | |||
| 8de7940335 | |||
| b6a1adf90e | |||
| 910b969894 | |||
| 476f17b84c | |||
| 1fe6cbdb4c | |||
| fb9f92e99e | |||
| 056182923a | |||
| be0e5f1dad | |||
| 5add1af33b | |||
| bda3b8dad2 | |||
| 5679b4b1db | |||
| 7730890e7c | |||
| 7d2ddd44c0 | |||
| ba8c42d70b | |||
| 09ad0121c6 | |||
| 23e839c80b | |||
| bed9fffa94 | |||
| 4c3e0f8b3c | |||
| c3bd6a3eea | |||
| 93294dcb18 | |||
| 9492994b8c | |||
| 0394a30dd7 | |||
| 83c0c03257 | |||
| 4c5aa19a8f | |||
| 8adb6e37eb | |||
| 12211e127c | |||
| 4345e93446 | |||
| 29bdd9ff26 | |||
| f6225c4983 | |||
| b49bcafd67 | |||
| 3cbca50cad | |||
| 018a6bd2c7 | |||
| 8c2cd7117c | |||
| 13e67ee7ca | |||
| a40e6dd5d8 | |||
| 89fd04febb | |||
| ee9fb8dfec | |||
| 1949e77df8 | |||
| 44079e1ce2 | |||
| 76a9eeda7c | |||
| 2dd9a49f05 | |||
| aa7a862631 | |||
| 66d1f3878b | |||
| 4f12259bc0 | |||
| c926f2bf05 | |||
| 1ee35b841d | |||
| 1a802d7428 | |||
| 056c3d6dad | |||
| 64760ea779 | |||
| d1027dd016 | |||
| 37862fdb5c | |||
| bbc8eae484 | |||
| 8fd4a508f8 | |||
| 26ac72d142 | |||
| bbebb43808 | |||
| 35deb90803 | |||
| 9fdc90cf13 | |||
| 6fe53d9508 | |||
| 9141f1b343 | |||
| 80804b9b49 | |||
| 034a67ff7e | |||
| 8d6cc854aa | |||
| 0ed02327b0 | |||
| fac32a6def | |||
| 1d0a51886f | |||
| e65a50161d | |||
| 950bfadaba | |||
| fbac926044 | |||
| 7efd573010 | |||
| 2b589d1ded | |||
| 63762ba279 | |||
| fa01fd6ed5 | |||
| 57598ec9de | |||
| 5ccd8663f6 | |||
| d663c8e3cc | |||
| 22a9d9a369 | |||
| b6ebdd9c7b | |||
| 94ac71d4ce | |||
| 9666e1cf41 | |||
| 35bd883d40 | |||
| 8faca75d06 | |||
| b1deaba0bd | |||
| 32417bbb7e | |||
| b3596e7471 | |||
| 9bbd6b21b9 | |||
| 7d35c5c5bb | |||
| 7d1f66e537 | |||
| 2e9f8b8064 | |||
| 5703f81bac | |||
| 7f8edee078 | |||
| 991aa67efb | |||
| f6e8e05dad | |||
| 09c07b24dc | |||
| 3699db53b5 | |||
| 0b2469eef2 | |||
| 1d892e6b99 | |||
| f4e87ed80b |
@@ -4,6 +4,10 @@ Fixes #
|
||||
|
||||
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
|
||||
|
||||
<!--
|
||||
@@ -14,6 +18,7 @@ Why this change is important?
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] allow Maintainers to edit PR (rebase, run coverage, help with tests, ...)
|
||||
- [ ] unit-test added (if change is algorithm)
|
||||
- [ ] functional test added/updated (if change is functional)
|
||||
- [ ] man page updated (if applicable)
|
||||
|
||||
+74
-26
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: CI
|
||||
|
||||
on:
|
||||
@@ -10,15 +11,38 @@ on:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
|
||||
shell: bash --noprofile --norc -eo pipefail {0}
|
||||
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
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-test
|
||||
mkdir -p out/coverage
|
||||
mv unit.out out/coverage/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unit-tests-coverage
|
||||
path: out/
|
||||
|
||||
test:
|
||||
name: "Test (Ubuntu 22.04)"
|
||||
name: "System Test"
|
||||
runs-on: ubuntu-22.04
|
||||
continue-on-error: false
|
||||
timeout-minutes: 30
|
||||
@@ -63,21 +87,10 @@ jobs:
|
||||
with:
|
||||
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"
|
||||
run: |
|
||||
COVERAGE_DIR=${{ runner.temp }} make bench
|
||||
mkdir -p out/coverage
|
||||
COVERAGE_DIR=$PWD/out/coverage make bench
|
||||
|
||||
- name: "Run System Tests"
|
||||
env:
|
||||
@@ -89,30 +102,63 @@ jobs:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: |
|
||||
sudo mkdir -p /srv ; sudo chown runner /srv
|
||||
COVERAGE_DIR=${{ runner.temp }} make system-test
|
||||
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"
|
||||
run: |
|
||||
go install github.com/wadey/gocovmerge@v0.0.0-20160331181800-b5bfa59ec0ad
|
||||
~/go/bin/gocovmerge unit.out ${{ runner.temp }}/*.out > coverage.txt
|
||||
# go install github.com/wadey/gocovmerge@v0.0.0-20160331181800-b5bfa59ec0ad
|
||||
# ~/go/bin/gocovmerge coverage/*.out > coverage.txt
|
||||
awk 'FNR==1 && NR!=1 {next} {print}' coverage/*.out > coverage.txt
|
||||
|
||||
- name: "Upload Code Coverage"
|
||||
uses: codecov/codecov-action@v2
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
uses: codecov/codecov-action@v7.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: coverage.txt
|
||||
fail_ci_if_error: true
|
||||
|
||||
|
||||
ci-debian-build:
|
||||
name: "Build"
|
||||
needs: test
|
||||
needs:
|
||||
- coverage
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
name: ["Debian 13/testing", "Debian 12/bookworm", "Debian 11/bullseye", "Debian 10/buster", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
||||
name: ["Debian 13/trixie", "Debian 12/bookworm", "Debian 11/bullseye", "Ubuntu 26.04", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
||||
arch: ["amd64", "i386" , "arm64" , "armhf"]
|
||||
include:
|
||||
- name: "Debian 13/testing"
|
||||
- name: "Debian 13/trixie"
|
||||
suite: trixie
|
||||
image: debian:trixie-slim
|
||||
- name: "Debian 12/bookworm"
|
||||
@@ -121,9 +167,9 @@ jobs:
|
||||
- name: "Debian 11/bullseye"
|
||||
suite: bullseye
|
||||
image: debian:bullseye-slim
|
||||
- name: "Debian 10/buster"
|
||||
suite: buster
|
||||
image: debian:buster-slim
|
||||
- name: "Ubuntu 26.04"
|
||||
suite: resolute
|
||||
image: ubuntu:26.04
|
||||
- name: "Ubuntu 24.04"
|
||||
suite: noble
|
||||
image: ubuntu:24.04
|
||||
@@ -135,6 +181,7 @@ jobs:
|
||||
image: ubuntu:20.04
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
options: --user root
|
||||
env:
|
||||
APT_LISTCHANGES_FRONTEND: none
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
@@ -226,7 +273,8 @@ jobs:
|
||||
|
||||
ci-binary-build:
|
||||
name: "Build"
|
||||
needs: test
|
||||
needs:
|
||||
- coverage
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Install and initialize swagger
|
||||
run: |
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
swag init -q --markdownFiles docs
|
||||
swag init -q --propertyStrategy pascalcase --markdownFiles docs
|
||||
shell: sh
|
||||
|
||||
- name: golangci-lint
|
||||
|
||||
@@ -69,3 +69,10 @@ List of contributors, in chronological order:
|
||||
* Leigh London (https://github.com/leighlondon)
|
||||
* Gordian Schoenherr (https://github.com/schoenherrg)
|
||||
* Silke Hofstra (https://github.com/silkeh)
|
||||
* Itay Porezky (https://github.com/itayporezky)
|
||||
* JupiterRider (https://github.com/JupiterRider)
|
||||
* Tobias Assarsson (https://github.com/daedaluz)
|
||||
* Yaksh Bariya (https://github.com/thunder-coding)
|
||||
* Brian Witt (https://github.com/bwitt)
|
||||
* Ales Bregar (https://github.com/abregar)
|
||||
* Tim Foerster (https://github.com/tonobo)
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ Please report unacceptable behavior on [https://github.com/aptly-dev/aptly/discu
|
||||
### List of Repositories
|
||||
|
||||
* [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
|
||||
fixtures for aptly functional tests
|
||||
|
||||
@@ -130,14 +130,14 @@ aptly version: 1.5.0+189+g0fc90dff
|
||||
|
||||
In order to run aptly unit tests, enter the following:
|
||||
```
|
||||
make docker-unit-tests
|
||||
make docker-unit-test
|
||||
```
|
||||
|
||||
#### Running system tests
|
||||
|
||||
In order to run aptly system tests, enter the following:
|
||||
```
|
||||
make docker-system-tests
|
||||
make docker-system-test
|
||||
```
|
||||
|
||||
#### Running golangci-lint
|
||||
|
||||
@@ -7,9 +7,23 @@ COVERAGE_DIR?=$(shell mktemp -d)
|
||||
GOOS=$(shell go env GOHOSTOS)
|
||||
GOARCH=$(shell go env GOHOSTARCH)
|
||||
|
||||
export PODMAN_USERNS = keep-id
|
||||
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
|
||||
@@ -61,9 +75,9 @@ azurite-start:
|
||||
azurite-stop:
|
||||
@kill `cat ~/.azurite.pid`
|
||||
|
||||
swagger: swagger-install
|
||||
swagger: #swagger-install
|
||||
# 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:
|
||||
# Install etcd
|
||||
@@ -101,13 +115,13 @@ test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify
|
||||
|
||||
system-test: prepare swagger etcd-install ## Run system tests
|
||||
# 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
|
||||
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
|
||||
test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz)
|
||||
# Run system tests
|
||||
PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE_ARG) $(TEST)
|
||||
PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long $(COVERAGE_ARG_TEST) $(CAPTURE_ARG) $(TEST)
|
||||
|
||||
bench:
|
||||
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
|
||||
@@ -117,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
|
||||
cp debian/aptly.conf ~/.aptly.conf
|
||||
sed -i /enable_swagger_endpoint/s/false/true/ ~/.aptly.conf
|
||||
PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs --generalInfo docs/swagger.conf' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142
|
||||
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
|
||||
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
|
||||
@@ -171,16 +186,16 @@ 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 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 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 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 (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 \
|
||||
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
|
||||
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
|
||||
@@ -189,27 +204,27 @@ docker-unit-test: ## Run unit tests in docker container (add TEST=regex to spec
|
||||
azurite-stop
|
||||
|
||||
docker-system-test: ## Run system tests in docker container (add TEST=t04_mirror or TEST=UpdateMirror26Test to run only specific tests)
|
||||
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \
|
||||
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper \
|
||||
azurite-start \
|
||||
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
|
||||
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
|
||||
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
|
||||
AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \
|
||||
AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \
|
||||
system-test TEST=$(TEST) \
|
||||
system-test TEST=$(TEST) CAPTURE=$(CAPTURE) COVERAGE_SKIP=$(COVERAGE_SKIP) \
|
||||
azurite-stop
|
||||
|
||||
docker-serve: ## Run development server (auto recompiling) on http://localhost:3142
|
||||
@docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper serve || true
|
||||
@$(DOCKER_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 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 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 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
|
||||
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
|
||||
|
||||
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::
|
||||
|
||||
@@ -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
|
||||
|
||||
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.
|
||||
|
||||
@@ -111,10 +111,8 @@ With configuration management systems:
|
||||
|
||||
- `Chef cookbook <https://github.com/hw-cookbooks/aptly>`_ by Aaron Baer
|
||||
(Heavy Water Operations, LLC)
|
||||
- `Puppet module <https://github.com/alphagov/puppet-aptly>`_ by
|
||||
Government Digital Services
|
||||
- `Puppet module <https://github.com/tubemogul/puppet-aptly>`_ by
|
||||
TubeMogul
|
||||
- `Puppet module <https://github.com/voxpupuli/puppet-aptly>`_ by
|
||||
Vox Pupuli
|
||||
- `SaltStack Formula <https://github.com/saltstack-formulas/aptly-formula>`_ by
|
||||
Forrest Alvarez and Brian Jackson
|
||||
- `Ansible role <https://github.com/aioue/ansible-role-aptly>`_ by Tom Paine
|
||||
|
||||
@@ -13,5 +13,6 @@ git push origin v$version master
|
||||
- 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
|
||||
- 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
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ func apiReady(isReady *atomic.Value) func(*gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
status := aptlyStatus{Status: "Aptly is ready"}
|
||||
status := aptlyStatus{Status: "Aptly is ready"}
|
||||
c.JSON(200, status)
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func truthy(value interface{}) bool {
|
||||
if value == nil {
|
||||
return false
|
||||
}
|
||||
switch v := value.(type) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
switch strings.ToLower(v) {
|
||||
case "n", "no", "f", "false", "0", "off":
|
||||
|
||||
+41
-2
@@ -13,6 +13,10 @@ import (
|
||||
"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 {
|
||||
path = filepath.Clean(path)
|
||||
for _, part := range strings.Split(path, string(filepath.Separator)) {
|
||||
@@ -114,34 +118,69 @@ func apiFilesUpload(c *gin.Context) {
|
||||
}
|
||||
|
||||
stored := []string{}
|
||||
openFiles := []*os.File{}
|
||||
|
||||
// Write all files first
|
||||
for _, files := range c.Request.MultipartForm.File {
|
||||
for _, file := range files {
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
// Close any files we've opened
|
||||
for _, f := range openFiles {
|
||||
_ = f.Close()
|
||||
}
|
||||
AbortWithJSONError(c, 500, err)
|
||||
return
|
||||
}
|
||||
defer func() { _ = src.Close() }()
|
||||
|
||||
destPath := filepath.Join(path, filepath.Base(file.Filename))
|
||||
dst, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
_ = src.Close()
|
||||
// Close any files we've opened
|
||||
for _, f := range openFiles {
|
||||
_ = f.Close()
|
||||
}
|
||||
AbortWithJSONError(c, 500, err)
|
||||
return
|
||||
}
|
||||
defer func() { _ = dst.Close() }()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
_ = src.Close()
|
||||
if err != nil {
|
||||
_ = dst.Close()
|
||||
// Close any files we've opened
|
||||
for _, f := range openFiles {
|
||||
_ = f.Close()
|
||||
}
|
||||
AbortWithJSONError(c, 500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Keep file open for batch sync
|
||||
openFiles = append(openFiles, dst)
|
||||
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()
|
||||
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)
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ type gpgAddKeyParams struct {
|
||||
// @Summary Add GPG Keys
|
||||
// @Description **Adds GPG keys to aptly keyring**
|
||||
// @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)
|
||||
|
||||
+47
-62
@@ -175,9 +175,9 @@ func apiMirrorsDrop(c *gin.Context) {
|
||||
name := c.Params.ByName("name")
|
||||
force := c.Request.URL.Query().Get("force") == "1"
|
||||
|
||||
// Phase 1: Pre-task validation (shallow load for 404 check only)
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
mirrorCollection := collectionFactory.RemoteRepoCollection()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
|
||||
repo, err := mirrorCollection.ByName(name)
|
||||
if err != nil {
|
||||
@@ -187,21 +187,34 @@ func apiMirrorsDrop(c *gin.Context) {
|
||||
|
||||
resources := []string{string(repo.Key())}
|
||||
taskName := fmt.Sprintf("Delete mirror %s", name)
|
||||
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err := repo.CheckLock()
|
||||
// Phase 2: Inside task lock - create fresh collections
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
|
||||
// Fresh load after lock acquired
|
||||
repo, err := taskMirrorCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||
}
|
||||
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||
}
|
||||
|
||||
if !force {
|
||||
snapshots := snapshotCollection.ByRemoteRepoSource(repo)
|
||||
// Fresh checks with current collections
|
||||
snapshots := taskSnapshotCollection.ByRemoteRepoSource(repo)
|
||||
|
||||
if len(snapshots) > 0 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
|
||||
}
|
||||
}
|
||||
|
||||
err = mirrorCollection.Drop(repo)
|
||||
err = taskMirrorCollection.Drop(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||
}
|
||||
@@ -333,26 +346,8 @@ func apiMirrorsPackages(c *gin.Context) {
|
||||
type mirrorUpdateParams struct {
|
||||
// Change mirror name to `Name`
|
||||
Name string ` json:"Name" example:"mirror1"`
|
||||
// Url of the archive to mirror
|
||||
ArchiveURL string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
|
||||
// Package query that is applied to mirror packages
|
||||
Filter string ` json:"Filter" example:"xserver-xorg"`
|
||||
// Limit mirror to those architectures, if not specified aptly would fetch all architectures
|
||||
Architectures []string ` json:"Architectures" example:"amd64"`
|
||||
// Components to mirror, if not specified aptly would fetch all components
|
||||
Components []string ` json:"Components" example:"main"`
|
||||
// Gpg keyring(s) for verifing Release file
|
||||
// Gpg keyring(s) for verifying Release file
|
||||
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
|
||||
// Set "true" to include dependencies of matching packages when filtering
|
||||
FilterWithDeps bool ` json:"FilterWithDeps"`
|
||||
// Set "true" to mirror source packages
|
||||
DownloadSources bool ` json:"DownloadSources"`
|
||||
// Set "true" to mirror udeb files
|
||||
DownloadUdebs bool ` json:"DownloadUdebs"`
|
||||
// Set "true" to skip checking if the given components are in the Release file
|
||||
SkipComponentCheck bool ` json:"SkipComponentCheck"`
|
||||
// Set "true" to skip checking if the given architectures are in the Release file
|
||||
SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"`
|
||||
// Set "true" to ignore checksum errors
|
||||
IgnoreChecksums bool ` json:"IgnoreChecksums"`
|
||||
// Set "true" to skip the verification of Release file signatures
|
||||
@@ -387,21 +382,14 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.RemoteRepoCollection()
|
||||
|
||||
remote, err = collection.ByName(c.Params.ByName("name"))
|
||||
name := c.Params.ByName("name")
|
||||
remote, err = collection.ByName(name)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 404, err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
log.Info().Msgf("%s: Starting mirror update", b.Name)
|
||||
@@ -410,6 +398,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Pre-task validation of new name if provided
|
||||
if b.Name != remote.Name {
|
||||
_, err = collection.ByName(b.Name)
|
||||
if err == nil {
|
||||
@@ -418,27 +407,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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
||||
@@ -447,9 +415,26 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
|
||||
resources := []string{string(remote.Key())}
|
||||
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
// Phase 2: Inside task lock - create fresh factory
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.RemoteRepoCollection()
|
||||
|
||||
// Fresh load after lock acquired (use captured `name` variable, not gin context)
|
||||
remote, err := taskCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
// Fresh rename check inside lock (if renaming)
|
||||
if b.Name != remote.Name {
|
||||
_, err := taskCollection.ByName(b.Name)
|
||||
if err == nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)
|
||||
}
|
||||
}
|
||||
|
||||
downloader := context.NewDownloader(out)
|
||||
err := remote.Fetch(downloader, verifier, b.IgnoreSignatures)
|
||||
err = remote.Fetch(downloader, verifier, b.IgnoreSignatures)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -461,7 +446,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, b.SkipComponentCheck)
|
||||
err = remote.DownloadPackageIndexes(out, downloader, verifier, taskCollectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -480,8 +465,8 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
||||
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
|
||||
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), taskCollectionFactory.PackageCollection(),
|
||||
taskCollectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -491,12 +476,12 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
e := context.ReOpenDatabase()
|
||||
if e == nil {
|
||||
remote.MarkAsIdle()
|
||||
_ = collection.Update(remote)
|
||||
_ = taskCollection.Update(remote)
|
||||
}
|
||||
}()
|
||||
|
||||
remote.MarkAsUpdating()
|
||||
err = collection.Update(remote)
|
||||
err = taskCollection.Update(remote)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -600,7 +585,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
}
|
||||
|
||||
// and import it back to the pool
|
||||
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, collectionFactory.ChecksumCollection(nil))
|
||||
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, taskCollectionFactory.ChecksumCollection(nil))
|
||||
if err != nil {
|
||||
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
|
||||
pushError(err)
|
||||
@@ -653,8 +638,8 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
}
|
||||
|
||||
log.Info().Msgf("%s: Finalizing download...", b.Name)
|
||||
_ = remote.FinalizeDownload(collectionFactory, out)
|
||||
err = collectionFactory.RemoteRepoCollection().Update(remote)
|
||||
_ = remote.FinalizeDownload(taskCollectionFactory, out)
|
||||
err = taskCollection.Update(remote)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
+339
-181
@@ -16,8 +16,8 @@ import (
|
||||
type signingParams struct {
|
||||
// Don't sign published repository
|
||||
Skip bool ` json:"Skip" example:"false"`
|
||||
// GPG key ID to use when signing the release, if not specified default key is used
|
||||
GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"`
|
||||
// GPG 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:"KEY_ID_a, KEY_ID_b"`
|
||||
// GPG keyring to use (instead of default)
|
||||
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
|
||||
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
|
||||
@@ -41,7 +41,21 @@ func getSigner(options *signingParams) (pgp.Signer, error) {
|
||||
}
|
||||
|
||||
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.SetPassphrase(options.Passphrase, options.PassphraseFile)
|
||||
|
||||
@@ -110,7 +124,7 @@ func apiPublishList(c *gin.Context) {
|
||||
// @Description See also: `aptly publish show`
|
||||
// @Tags Publish
|
||||
// @Produce json
|
||||
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambigious in URLs"
|
||||
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambiguous in URLs"
|
||||
// @Param distribution path string true "distribution name"
|
||||
// @Success 200 {object} deb.PublishedRepo
|
||||
// @Failure 404 {object} Error "Published repository not found"
|
||||
@@ -146,10 +160,6 @@ type publishedRepoCreateParams struct {
|
||||
Sources []sourceParams `binding:"required" json:"Sources"`
|
||||
// Distribution name, if missing Aptly would try to guess from sources
|
||||
Distribution string ` json:"Distribution" example:"bookworm"`
|
||||
// Value of Label: field in published repository stanza
|
||||
Label string ` json:"Label" example:""`
|
||||
// Value of Origin: field in published repository stanza
|
||||
Origin string ` json:"Origin" example:""`
|
||||
// when publishing, overwrite files in pool/ directory without notice
|
||||
ForceOverwrite bool ` json:"ForceOverwrite" example:"false"`
|
||||
// Override list of published architectures
|
||||
@@ -182,7 +192,7 @@ type publishedRepoCreateParams struct {
|
||||
// @Description **Example:**
|
||||
// @Description ```
|
||||
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos
|
||||
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Label":"","Origin":"","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
|
||||
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
|
||||
// @Description ```
|
||||
// @Description
|
||||
// @Description See also: `aptly publish create`
|
||||
@@ -249,7 +259,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources = append(resources, string(snapshot.ResourceKey()))
|
||||
resources = append(resources, string(snapshot.Key()))
|
||||
sources = append(sources, snapshot)
|
||||
}
|
||||
} else if b.SourceKind == deb.SourceLocalRepo {
|
||||
@@ -280,11 +290,24 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
multiDist = *b.MultiDist
|
||||
}
|
||||
|
||||
collection := collectionFactory.PublishedRepoCollection()
|
||||
// Non-MultiDist publishes share a single pool/ directory under the
|
||||
// prefix. Lock at the prefix level so that concurrent publish/drop
|
||||
// operations on sibling distributions cannot race during cleanup.
|
||||
if !multiDist {
|
||||
storagePrefix := prefix
|
||||
if storage != "" {
|
||||
storagePrefix = storage + ":" + prefix
|
||||
}
|
||||
|
||||
resources = append(resources, deb.PrefixPoolLockKey(storagePrefix))
|
||||
}
|
||||
|
||||
taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"",
|
||||
b.SourceKind, param, b.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`))
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||
|
||||
taskDetail := task.PublishDetail{
|
||||
Detail: detail,
|
||||
}
|
||||
@@ -296,10 +319,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
for _, source := range sources {
|
||||
switch s := source.(type) {
|
||||
case *deb.Snapshot:
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
snapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
err = snapshotCollection.LoadComplete(s)
|
||||
case *deb.LocalRepo:
|
||||
localCollection := collectionFactory.LocalRepoCollection()
|
||||
localCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
err = localCollection.LoadComplete(s)
|
||||
default:
|
||||
err = fmt.Errorf("unexpected type for source: %T", source)
|
||||
@@ -309,23 +332,17 @@ 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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
resources = append(resources, string(published.Key()))
|
||||
|
||||
if b.Origin != "" {
|
||||
published.Origin = b.Origin
|
||||
}
|
||||
if b.NotAutomatic != "" {
|
||||
published.NotAutomatic = b.NotAutomatic
|
||||
}
|
||||
if b.ButAutomaticUpgrades != "" {
|
||||
published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
|
||||
}
|
||||
published.Label = b.Label
|
||||
|
||||
published.SkipContents = context.Config().SkipContentsPublishing
|
||||
if b.SkipContents != nil {
|
||||
@@ -341,18 +358,18 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
published.AcquireByHash = *b.AcquireByHash
|
||||
}
|
||||
|
||||
duplicate := collection.CheckDuplicate(published)
|
||||
duplicate := taskCollection.CheckDuplicate(published)
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -393,7 +410,6 @@ type publishedRepoUpdateSwitchParams struct {
|
||||
// @Description
|
||||
// @Description See also: `aptly publish update` / `aptly publish switch`
|
||||
// @Tags Publish
|
||||
// @Produce json
|
||||
// @Param prefix path string true "publishing prefix"
|
||||
// @Param distribution path string true "distribution name"
|
||||
// @Param _async query bool false "Run in background and return task object"
|
||||
@@ -425,6 +441,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.PublishedRepoCollection()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||
|
||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
@@ -432,48 +449,76 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
|
||||
if published.SourceKind == deb.SourceLocalRepo {
|
||||
if len(b.Snapshots) > 0 {
|
||||
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
||||
return
|
||||
}
|
||||
} else if published.SourceKind == deb.SourceSnapshot {
|
||||
for _, snapshotInfo := range b.Snapshots {
|
||||
_, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
||||
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 _, snapshotInfo := range b.Snapshots {
|
||||
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
||||
if err2 != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||
return
|
||||
}
|
||||
resources = append(resources, string(snapshot.Key()))
|
||||
}
|
||||
} else {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
|
||||
return
|
||||
}
|
||||
|
||||
if b.SkipContents != nil {
|
||||
published.SkipContents = *b.SkipContents
|
||||
// Non-MultiDist distributions share a single pool/ directory under the
|
||||
// prefix. Acquire the prefix-level pool lock so that concurrent updates
|
||||
// on sibling distributions are serialised and cannot race during cleanup.
|
||||
if !published.MultiDist {
|
||||
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
|
||||
}
|
||||
|
||||
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())}
|
||||
// Field mutations and fresh DB load are deferred to inside the task so
|
||||
// they always operate on a consistent state after the lock is held.
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||
|
||||
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// Capture MultiDist before mutations to detect a false→true transition.
|
||||
prevMultiDist := published.MultiDist
|
||||
|
||||
// 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.MultiDist != nil {
|
||||
published.MultiDist = *b.MultiDist
|
||||
}
|
||||
|
||||
revision := published.ObtainRevision()
|
||||
sources := revision.Sources
|
||||
|
||||
@@ -485,17 +530,17 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
result, err := published.Update(collectionFactory, out)
|
||||
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, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||
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 = collection.Update(published)
|
||||
err = taskCollection.Update(published)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -503,10 +548,19 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
||||
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
||||
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
||||
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
// When MultiDist is toggled, the old pool layout still has files that
|
||||
// CleanupPrefixComponentFiles won't touch (it only scans the new layout).
|
||||
// Run a second pass over the previous layout to remove stale files.
|
||||
if prevMultiDist != published.MultiDist {
|
||||
err = taskCollection.CleanupAfterMultiDistToggle(context, published, prevMultiDist, cleanComponents, taskCollectionFactory, out)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to clean legacy pool: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
||||
@@ -551,10 +605,19 @@ func apiPublishDrop(c *gin.Context) {
|
||||
}
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
// Non-MultiDist distributions share a single pool/ directory under the
|
||||
// prefix. Acquire the prefix-level pool lock so that a drop cannot race
|
||||
// with a concurrent update or drop of a sibling distribution during cleanup.
|
||||
if !published.MultiDist {
|
||||
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
|
||||
}
|
||||
taskName := fmt.Sprintf("Delete published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err := collection.Remove(context, storage, prefix, distribution,
|
||||
collectionFactory, out, force, skipCleanup)
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||
|
||||
err := taskCollection.Remove(context, storage, prefix, distribution,
|
||||
taskCollectionFactory, out, force, skipCleanup)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
@@ -590,43 +653,52 @@ func apiPublishAddSource(c *gin.Context) {
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||
|
||||
if c.Bind(&b) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to create: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if c.Bind(&b) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
revision := published.ObtainRevision()
|
||||
sources := revision.Sources
|
||||
|
||||
component := b.Component
|
||||
name := b.Name
|
||||
|
||||
_, exists := sources[component]
|
||||
if exists {
|
||||
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to create: Component '%s' already exists", component))
|
||||
return
|
||||
}
|
||||
|
||||
sources[component] = name
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ 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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -708,39 +780,48 @@ func apiPublishSetSources(c *gin.Context) {
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||
|
||||
if c.Bind(&b) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if c.Bind(&b) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
revision := published.ObtainRevision()
|
||||
sources := make(map[string]string, len(b))
|
||||
revision.Sources = sources
|
||||
|
||||
for _, source := range b {
|
||||
component := source.Component
|
||||
name := source.Name
|
||||
sources[component] = name
|
||||
}
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ 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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -773,24 +854,33 @@ func apiPublishDropChanges(c *gin.Context) {
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
published.DropRevision()
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ 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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -826,51 +916,58 @@ func apiPublishUpdateSource(c *gin.Context) {
|
||||
param := slashEscape(c.Params.ByName("prefix"))
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||
component := slashEscape(c.Params.ByName("component"))
|
||||
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()
|
||||
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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
revision := published.ObtainRevision()
|
||||
sources := revision.Sources
|
||||
|
||||
_, exists := sources[component]
|
||||
if !exists {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: Component '%s' does not exist", component))
|
||||
return
|
||||
}
|
||||
|
||||
b.Component = component
|
||||
b.Name = revision.Sources[component]
|
||||
|
||||
if c.Bind(&b) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if b.Component != component {
|
||||
delete(sources, component)
|
||||
}
|
||||
|
||||
component = b.Component
|
||||
name := b.Name
|
||||
sources[component] = name
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ 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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -907,33 +1004,41 @@ func apiPublishRemoveSource(c *gin.Context) {
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
revision := published.ObtainRevision()
|
||||
sources := revision.Sources
|
||||
|
||||
_, exists := sources[component]
|
||||
if !exists {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: Component '%s' does not exist", component))
|
||||
return
|
||||
}
|
||||
|
||||
delete(sources, component)
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ 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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -997,48 +1102,92 @@ func apiPublishUpdate(c *gin.Context) {
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
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)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if b.SkipContents != nil {
|
||||
published.SkipContents = *b.SkipContents
|
||||
}
|
||||
|
||||
if b.SkipBz2 != nil {
|
||||
published.SkipBz2 = *b.SkipBz2
|
||||
}
|
||||
|
||||
if b.AcquireByHash != nil {
|
||||
published.AcquireByHash = *b.AcquireByHash
|
||||
}
|
||||
|
||||
if b.MultiDist != nil {
|
||||
published.MultiDist = *b.MultiDist
|
||||
}
|
||||
|
||||
resources := []string{string(published.Key())}
|
||||
|
||||
// Non-MultiDist distributions share a single pool/ directory under the
|
||||
// prefix. Acquire the prefix-level pool lock so that concurrent updates
|
||||
// on sibling distributions are serialised and cannot race during cleanup.
|
||||
if !published.MultiDist {
|
||||
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
|
||||
}
|
||||
|
||||
// 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.Key()))
|
||||
}
|
||||
}
|
||||
|
||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
result, err := published.Update(collectionFactory, out)
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||
|
||||
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = collection.Update(published)
|
||||
// Capture MultiDist before mutations to detect a false→true transition.
|
||||
prevMultiDist := published.MultiDist
|
||||
|
||||
// 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.MultiDist != nil {
|
||||
published.MultiDist = *b.MultiDist
|
||||
}
|
||||
|
||||
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 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
@@ -1046,10 +1195,19 @@ func apiPublishUpdate(c *gin.Context) {
|
||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
||||
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
||||
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
||||
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
// When MultiDist is toggled, the old pool layout still has files that
|
||||
// CleanupPrefixComponentFiles won't touch (it only scans the new layout).
|
||||
// Run a second pass over the previous layout to remove stale files.
|
||||
if prevMultiDist != published.MultiDist {
|
||||
err = taskCollection.CleanupAfterMultiDistToggle(context, published, prevMultiDist, cleanComponents, taskCollectionFactory, out)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to clean legacy pool: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
||||
|
||||
@@ -0,0 +1,733 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/smira/flag"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// PublishedFileMissingSuite reproduces the exact bug where:
|
||||
// - Package import succeeds
|
||||
// - Metadata is updated (Packages.gz shows the package)
|
||||
// - Publish reports success
|
||||
// - BUT the .deb file is missing from the published pool directory
|
||||
// - Result: apt-get returns 404 when trying to download the package
|
||||
type PublishedFileMissingSuite struct {
|
||||
context *ctx.AptlyContext
|
||||
flags *flag.FlagSet
|
||||
configFile *os.File
|
||||
router http.Handler
|
||||
tempDir string
|
||||
poolPath string
|
||||
publicPath string
|
||||
}
|
||||
|
||||
var _ = Suite(&PublishedFileMissingSuite{})
|
||||
|
||||
func (s *PublishedFileMissingSuite) SetUpSuite(c *C) {
|
||||
aptly.Version = "publishedFileMissingTest"
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "aptly-published-missing-test")
|
||||
c.Assert(err, IsNil)
|
||||
s.tempDir = tempDir
|
||||
s.poolPath = filepath.Join(tempDir, "pool")
|
||||
s.publicPath = filepath.Join(tempDir, "public")
|
||||
|
||||
file, err := os.CreateTemp("", "aptly-published-missing-config")
|
||||
c.Assert(err, IsNil)
|
||||
s.configFile = file
|
||||
|
||||
config := gin.H{
|
||||
"rootDir": tempDir,
|
||||
"downloadDir": filepath.Join(tempDir, "download"),
|
||||
"architectures": []string{"amd64"},
|
||||
"dependencyFollowSuggests": false,
|
||||
"dependencyFollowRecommends": false,
|
||||
"gpgDisableSign": true,
|
||||
"gpgDisableVerify": true,
|
||||
"gpgProvider": "internal",
|
||||
"skipLegacyPool": true,
|
||||
"enableMetricsEndpoint": false,
|
||||
}
|
||||
|
||||
jsonString, err := json.Marshal(config)
|
||||
c.Assert(err, IsNil)
|
||||
_, err = file.Write(jsonString)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
flags := flag.NewFlagSet("publishedFileMissingTestFlags", flag.ContinueOnError)
|
||||
flags.Bool("no-lock", true, "disable database locking for test")
|
||||
flags.Int("db-open-attempts", 3, "dummy")
|
||||
flags.String("config", s.configFile.Name(), "config file")
|
||||
flags.String("architectures", "", "dummy")
|
||||
s.flags = flags
|
||||
|
||||
context, err := ctx.NewContext(s.flags)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.context = context
|
||||
s.router = Router(context)
|
||||
}
|
||||
|
||||
func (s *PublishedFileMissingSuite) TearDownSuite(c *C) {
|
||||
if s.configFile != nil {
|
||||
_ = os.Remove(s.configFile.Name())
|
||||
}
|
||||
if s.context != nil {
|
||||
s.context.Shutdown()
|
||||
}
|
||||
if s.tempDir != "" {
|
||||
_ = os.RemoveAll(s.tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PublishedFileMissingSuite) SetUpTest(c *C) {
|
||||
collectionFactory := s.context.NewCollectionFactory()
|
||||
|
||||
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||
_ = localRepoCollection.ForEach(func(repo *deb.LocalRepo) error {
|
||||
_ = localRepoCollection.Drop(repo)
|
||||
return nil
|
||||
})
|
||||
|
||||
publishedCollection := collectionFactory.PublishedRepoCollection()
|
||||
_ = publishedCollection.ForEach(func(published *deb.PublishedRepo) error {
|
||||
_ = publishedCollection.Remove(s.context, published.Storage, published.Prefix,
|
||||
published.Distribution, collectionFactory, nil, true, true)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PublishedFileMissingSuite) TearDownTest(c *C) {
|
||||
s.SetUpTest(c)
|
||||
}
|
||||
|
||||
func (s *PublishedFileMissingSuite) httpRequest(c *C, method string, url string, body []byte) *httptest.ResponseRecorder {
|
||||
w := httptest.NewRecorder()
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
if body != nil {
|
||||
req, err = http.NewRequest(method, url, bytes.NewReader(body))
|
||||
} else {
|
||||
req, err = http.NewRequest(method, url, nil)
|
||||
}
|
||||
c.Assert(err, IsNil)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func (s *PublishedFileMissingSuite) createDebPackage(c *C, uploadID, packageName, version string) {
|
||||
uploadPath := s.context.UploadPath()
|
||||
uploadDir := filepath.Join(uploadPath, uploadID)
|
||||
err := os.MkdirAll(uploadDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "deb-build")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = os.RemoveAll(tempDir) }()
|
||||
|
||||
debianDir := filepath.Join(tempDir, "DEBIAN")
|
||||
err = os.MkdirAll(debianDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
controlContent := fmt.Sprintf(`Package: %s
|
||||
Version: %s
|
||||
Section: libs
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Maintainer: Test <test@example.com>
|
||||
Description: Test package
|
||||
Test package for published file missing bug.
|
||||
`, packageName, version)
|
||||
|
||||
err = os.WriteFile(filepath.Join(debianDir, "control"), []byte(controlContent), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
usrDir := filepath.Join(tempDir, "usr", "lib")
|
||||
err = os.MkdirAll(usrDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
err = os.WriteFile(filepath.Join(usrDir, "lib.so"), []byte("library"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
debFile := filepath.Join(uploadDir, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
cmd := exec.Command("dpkg-deb", "--build", tempDir, debFile)
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
// TestPublishedFileGoMissing reproduces the exact production bug
|
||||
func (s *PublishedFileMissingSuite) TestPublishedFileGoMissing(c *C) {
|
||||
c.Log("=== Reproducing: Package in metadata but 404 on download ===")
|
||||
|
||||
// Create and publish a repository
|
||||
repoName := "test-repo"
|
||||
distribution := "bullseye"
|
||||
|
||||
createBody, _ := json.Marshal(gin.H{
|
||||
"Name": repoName,
|
||||
"DefaultDistribution": distribution,
|
||||
"DefaultComponent": "main",
|
||||
})
|
||||
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create repo: %s", resp.Body.String()))
|
||||
|
||||
publishBody, _ := json.Marshal(gin.H{
|
||||
"SourceKind": "local",
|
||||
"Distribution": distribution,
|
||||
"Architectures": []string{"amd64"},
|
||||
"Sources": []gin.H{
|
||||
{"Component": "main", "Name": repoName},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
})
|
||||
resp = s.httpRequest(c, "POST", "/api/publish/hrt", publishBody)
|
||||
c.Assert(resp.Code, Equals, 201, Commentf("Failed to publish: %s", resp.Body.String()))
|
||||
|
||||
// Create package
|
||||
packageName := "hrt-libblobbyclient1"
|
||||
version := "20250926.152427+hrtdeb11"
|
||||
uploadID := "test-upload-1"
|
||||
|
||||
s.createDebPackage(c, uploadID, packageName, version)
|
||||
|
||||
// Add package
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoName, uploadID), nil)
|
||||
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package: %s", resp.Body.String()))
|
||||
|
||||
// Update publish
|
||||
updateBody, _ := json.Marshal(gin.H{
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"ForceOverwrite": true,
|
||||
})
|
||||
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/hrt/%s", distribution), updateBody)
|
||||
c.Assert(resp.Code, Equals, 200, Commentf("Failed to update publish: %s", resp.Body.String()))
|
||||
|
||||
// Now check if the file is actually accessible in the published location
|
||||
publishedStorage := s.context.GetPublishedStorage("")
|
||||
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||
|
||||
// Expected file path: hrt/pool/main/h/hrt-libblobbyclient1/hrt-libblobbyclient1_20250926.152427+hrtdeb11_amd64.deb
|
||||
expectedPath := filepath.Join(publicPath, "hrt", "pool", "main", "h", packageName,
|
||||
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
|
||||
c.Logf("Checking for published file at: %s", expectedPath)
|
||||
|
||||
fileInfo, err := os.Stat(expectedPath)
|
||||
fileExists := err == nil
|
||||
|
||||
c.Logf("File exists: %v", fileExists)
|
||||
if fileExists {
|
||||
c.Logf("File size: %d bytes", fileInfo.Size())
|
||||
}
|
||||
|
||||
// Check metadata
|
||||
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repoName), nil)
|
||||
var packages []string
|
||||
err = json.Unmarshal(resp.Body.Bytes(), &packages)
|
||||
c.Assert(err, IsNil)
|
||||
c.Logf("Packages in metadata: %d", len(packages))
|
||||
|
||||
// THE BUG: Metadata says package exists, but file is missing from published location
|
||||
if len(packages) > 0 && !fileExists {
|
||||
c.Logf("★★★ BUG REPRODUCED! ★★★")
|
||||
c.Logf("Metadata shows %d package(s) but file is missing at: %s", len(packages), expectedPath)
|
||||
c.Logf("This is exactly what causes: 404 Not Found [IP: 10.20.72.62 3142]")
|
||||
|
||||
c.Fatal("BUG CONFIRMED: Package in metadata but missing from published directory!")
|
||||
}
|
||||
|
||||
c.Assert(fileExists, Equals, true, Commentf(
|
||||
"Published file should exist at %s when package is in metadata", expectedPath))
|
||||
}
|
||||
|
||||
// TestConcurrentPublishRace tries to trigger the race with concurrent publishes
|
||||
func (s *PublishedFileMissingSuite) TestConcurrentPublishRace(c *C) {
|
||||
c.Log("=== Testing concurrent publish race condition ===")
|
||||
|
||||
const numIterations = 4
|
||||
|
||||
for iteration := 0; iteration < numIterations; iteration++ {
|
||||
c.Logf("--- Iteration %d/%d ---", iteration+1, numIterations)
|
||||
|
||||
// Create repo
|
||||
repoName := fmt.Sprintf("race-repo-%d", iteration)
|
||||
distribution := fmt.Sprintf("dist-%d", iteration)
|
||||
|
||||
createBody, _ := json.Marshal(gin.H{
|
||||
"Name": repoName,
|
||||
"DefaultDistribution": distribution,
|
||||
"DefaultComponent": "main",
|
||||
})
|
||||
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||
c.Assert(resp.Code, Equals, 201)
|
||||
|
||||
publishBody, _ := json.Marshal(gin.H{
|
||||
"SourceKind": "local",
|
||||
"Distribution": distribution,
|
||||
"Architectures": []string{"amd64"},
|
||||
"Sources": []gin.H{
|
||||
{"Component": "main", "Name": repoName},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
})
|
||||
resp = s.httpRequest(c, "POST", "/api/publish/concurrent", publishBody)
|
||||
c.Assert(resp.Code, Equals, 201)
|
||||
|
||||
// Create multiple packages
|
||||
var wg sync.WaitGroup
|
||||
numPackages := 5
|
||||
|
||||
for i := 0; i < numPackages; i++ {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
|
||||
packageName := fmt.Sprintf("pkg-%d-%d", iteration, idx)
|
||||
version := "1.0.0"
|
||||
uploadID := fmt.Sprintf("upload-%d-%d", iteration, idx)
|
||||
|
||||
s.createDebPackage(c, uploadID, packageName, version)
|
||||
|
||||
// Add package
|
||||
resp := s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoName, uploadID), nil)
|
||||
c.Logf("Package %d add: %d", idx, resp.Code)
|
||||
|
||||
// Small delay
|
||||
time.Sleep(time.Duration(5+idx*2) * time.Millisecond)
|
||||
|
||||
// Publish
|
||||
updateBody, _ := json.Marshal(gin.H{
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"ForceOverwrite": true,
|
||||
})
|
||||
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/concurrent/%s", distribution), updateBody)
|
||||
c.Logf("Publish %d: %d", idx, resp.Code)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check all packages
|
||||
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repoName), nil)
|
||||
var packages []string
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &packages)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Check published files
|
||||
publishedStorage := s.context.GetPublishedStorage("")
|
||||
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||
|
||||
missingFiles := []string{}
|
||||
for i := 0; i < numPackages; i++ {
|
||||
packageName := fmt.Sprintf("pkg-%d-%d", iteration, i)
|
||||
version := "1.0.0"
|
||||
|
||||
// Calculate pool path
|
||||
poolSubdir := string(packageName[0])
|
||||
expectedPath := filepath.Join(publicPath, "concurrent", "pool", "main", poolSubdir, packageName,
|
||||
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
|
||||
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||
missingFiles = append(missingFiles, expectedPath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingFiles) > 0 {
|
||||
c.Logf("★★★ BUG DETECTED in iteration %d/%d! ★★★", iteration+1, numIterations)
|
||||
c.Logf("Metadata shows %d packages, but %d files are MISSING:", len(packages), len(missingFiles))
|
||||
for i, f := range missingFiles {
|
||||
c.Logf(" [iter %d] File MISSING %d/%d: %s", iteration+1, i+1, len(missingFiles), f)
|
||||
}
|
||||
|
||||
c.Fatalf("BUG REPRODUCED in iteration %d/%d: %d published files missing", iteration+1, numIterations, len(missingFiles))
|
||||
} else {
|
||||
c.Logf("[iter %d/%d] All %d files present - OK", iteration+1, numIterations, numPackages)
|
||||
}
|
||||
}
|
||||
|
||||
c.Logf("All %d iterations passed - bug not reproduced with current timing", numIterations)
|
||||
}
|
||||
|
||||
// TestIdenticalPackageRace tests the specific case of identical SHA256 packages
|
||||
func (s *PublishedFileMissingSuite) TestIdenticalPackageRace(c *C) {
|
||||
c.Log("=== AGGRESSIVE test: identical package (same SHA256) race ===")
|
||||
|
||||
const numIterations = 4
|
||||
packageName := "shared-package"
|
||||
|
||||
for iter := 0; iter < numIterations; iter++ {
|
||||
c.Logf("Iteration %d/%d", iter+1, numIterations)
|
||||
|
||||
// Create two repos that will get the SAME package (unique per iteration)
|
||||
repos := []string{fmt.Sprintf("identical-a-%d", iter), fmt.Sprintf("identical-b-%d", iter)}
|
||||
dists := []string{fmt.Sprintf("dist-a-%d", iter), fmt.Sprintf("dist-b-%d", iter)}
|
||||
|
||||
for i := range repos {
|
||||
createBody, _ := json.Marshal(gin.H{
|
||||
"Name": repos[i],
|
||||
"DefaultDistribution": dists[i],
|
||||
"DefaultComponent": "main",
|
||||
})
|
||||
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||
c.Assert(resp.Code, Equals, 201)
|
||||
|
||||
publishBody, _ := json.Marshal(gin.H{
|
||||
"SourceKind": "local",
|
||||
"Distribution": dists[i],
|
||||
"Architectures": []string{"amd64"},
|
||||
"Sources": []gin.H{
|
||||
{"Component": "main", "Name": repos[i]},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"SkipBz2": true,
|
||||
})
|
||||
resp = s.httpRequest(c, "POST", "/api/publish/identical", publishBody)
|
||||
c.Assert(resp.Code, Equals, 201)
|
||||
}
|
||||
|
||||
// Create IDENTICAL package file with UNIQUE VERSION per iteration
|
||||
version := fmt.Sprintf("1.0.%d", iter)
|
||||
uploadID1 := fmt.Sprintf("identical-upload-1-%d", iter)
|
||||
uploadID2 := fmt.Sprintf("identical-upload-2-%d", iter)
|
||||
|
||||
s.createDebPackage(c, uploadID1, packageName, version)
|
||||
|
||||
// Copy to second upload (same SHA256)
|
||||
uploadPath := s.context.UploadPath()
|
||||
src := filepath.Join(uploadPath, uploadID1, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
destDir := filepath.Join(uploadPath, uploadID2)
|
||||
err := os.MkdirAll(destDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
dest := filepath.Join(destDir, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
|
||||
srcData, readErr := os.ReadFile(src)
|
||||
c.Assert(readErr, IsNil)
|
||||
err = os.WriteFile(dest, srcData, 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Race: add and publish both simultaneously
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repos[0], uploadID1), nil)
|
||||
updateBody, _ := json.Marshal(gin.H{"Signing": gin.H{"Skip": true}, "ForceOverwrite": true, "SkipBz2": true})
|
||||
s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/identical/%s", dists[0]), updateBody)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repos[1], uploadID2), nil)
|
||||
updateBody, _ := json.Marshal(gin.H{"Signing": gin.H{"Skip": true}, "ForceOverwrite": true, "SkipBz2": true})
|
||||
s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/identical/%s", dists[1]), updateBody)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
c.Logf("[iter %d] All operations complete", iter)
|
||||
|
||||
// Check the shared pool location
|
||||
publishedStorage := s.context.GetPublishedStorage("")
|
||||
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||
|
||||
poolSubdir := string(packageName[0])
|
||||
sharedPoolPath := filepath.Join(publicPath, "identical", "pool", "main", poolSubdir, packageName,
|
||||
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
|
||||
fileInfo, err := os.Stat(sharedPoolPath)
|
||||
fileExists := err == nil
|
||||
|
||||
if fileExists {
|
||||
c.Logf("[iter %d] File EXISTS at %s (size: %d)", iter, sharedPoolPath, fileInfo.Size())
|
||||
} else {
|
||||
c.Logf("[iter %d] File MISSING at %s (error: %v)", iter, sharedPoolPath, err)
|
||||
}
|
||||
|
||||
// Check metadata
|
||||
var packagesA, packagesB []string
|
||||
resp := s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repos[0]), nil)
|
||||
err = json.Unmarshal(resp.Body.Bytes(), &packagesA)
|
||||
c.Assert(err, IsNil)
|
||||
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repos[1]), nil)
|
||||
err = json.Unmarshal(resp.Body.Bytes(), &packagesB)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Logf("[iter %d] Packages in metadata: A=%d, B=%d", iter, len(packagesA), len(packagesB))
|
||||
|
||||
// THE BUG: Both repos show packages in metadata, but the shared pool file is missing
|
||||
if (len(packagesA) > 0 || len(packagesB) > 0) && !fileExists {
|
||||
c.Logf("★★★ BUG REPRODUCED in iteration %d! ★★★", iter+1)
|
||||
c.Logf("Packages in metadata A: %d, B: %d", len(packagesA), len(packagesB))
|
||||
c.Logf("Shared pool file exists: %v", fileExists)
|
||||
c.Logf("Pool path: %s", sharedPoolPath)
|
||||
|
||||
// List what files ARE in the pool directory
|
||||
poolDir := filepath.Dir(sharedPoolPath)
|
||||
if entries, err := os.ReadDir(poolDir); err == nil {
|
||||
c.Logf("Files in pool directory %s:", poolDir)
|
||||
for _, entry := range entries {
|
||||
c.Logf(" - %s", entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
c.Fatalf("Metadata shows packages but shared pool file is missing (iteration %d)", iter+1)
|
||||
}
|
||||
}
|
||||
|
||||
c.Logf("All %d iterations passed - bug not reproduced", numIterations)
|
||||
}
|
||||
|
||||
// TestConcurrentSnapshotPublishToSamePrefix reproduces the EXACT production bug:
|
||||
// Multiple snapshots are published concurrently to the SAME prefix but different distributions.
|
||||
// Example from production logs:
|
||||
// - trixie-pgdg published to "external/postgres-auto/trixie"
|
||||
// - bullseye-pgdg published to "external/postgres-auto/bullseye"
|
||||
// Both share the same pool directory, causing cleanup race conditions.
|
||||
func (s *PublishedFileMissingSuite) TestConcurrentSnapshotPublishToSamePrefix(c *C) {
|
||||
const numIterations = 4
|
||||
|
||||
for iter := 0; iter < numIterations; iter++ {
|
||||
c.Logf("--- Iteration %d/%d ---", iter+1, numIterations)
|
||||
|
||||
// Create two repos with different packages (simulating trixie-pgdg and bullseye-pgdg)
|
||||
repoTrixie := fmt.Sprintf("trixie-pgdg-%d", iter)
|
||||
repoBullseye := fmt.Sprintf("bullseye-pgdg-%d", iter)
|
||||
|
||||
// Create trixie repo
|
||||
createBody, _ := json.Marshal(gin.H{
|
||||
"Name": repoTrixie,
|
||||
"DefaultDistribution": "trixie",
|
||||
"DefaultComponent": "main",
|
||||
})
|
||||
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create trixie repo"))
|
||||
|
||||
// Create bullseye repo
|
||||
createBody, _ = json.Marshal(gin.H{
|
||||
"Name": repoBullseye,
|
||||
"DefaultDistribution": "bullseye",
|
||||
"DefaultComponent": "main",
|
||||
})
|
||||
resp = s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create bullseye repo"))
|
||||
|
||||
// Add packages to both repos
|
||||
numPackages := 3
|
||||
|
||||
// Add packages to trixie repo
|
||||
for i := 0; i < numPackages; i++ {
|
||||
packageName := fmt.Sprintf("postgresql-17-trixie-pkg%d", i)
|
||||
version := fmt.Sprintf("17.0.%d", iter)
|
||||
uploadID := fmt.Sprintf("trixie-upload-%d-%d", iter, i)
|
||||
|
||||
s.createDebPackage(c, uploadID, packageName, version)
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoTrixie, uploadID), nil)
|
||||
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package to trixie"))
|
||||
}
|
||||
|
||||
// Add packages to bullseye repo
|
||||
for i := 0; i < numPackages; i++ {
|
||||
packageName := fmt.Sprintf("postgresql-17-bullseye-pkg%d", i)
|
||||
version := fmt.Sprintf("17.0.%d", iter)
|
||||
uploadID := fmt.Sprintf("bullseye-upload-%d-%d", iter, i)
|
||||
|
||||
s.createDebPackage(c, uploadID, packageName, version)
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoBullseye, uploadID), nil)
|
||||
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package to bullseye"))
|
||||
}
|
||||
|
||||
// Create snapshots from both repos
|
||||
snapshotTrixie := fmt.Sprintf("%s-snap", repoTrixie)
|
||||
snapshotBullseye := fmt.Sprintf("%s-snap", repoBullseye)
|
||||
|
||||
createSnapshotBody, _ := json.Marshal(gin.H{"Name": snapshotTrixie})
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/snapshots", repoTrixie), createSnapshotBody)
|
||||
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create trixie snapshot"))
|
||||
|
||||
createSnapshotBody, _ = json.Marshal(gin.H{"Name": snapshotBullseye})
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/snapshots", repoBullseye), createSnapshotBody)
|
||||
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create bullseye snapshot"))
|
||||
|
||||
// Publish both snapshots CONCURRENTLY to the SAME prefix
|
||||
// This mimics production where both are published to "external/postgres-auto"
|
||||
// Use the SAME prefix across all iterations to trigger the race more aggressively
|
||||
sharedPrefix := "postgres-auto"
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var trixiePublishCode, bullseyePublishCode int
|
||||
|
||||
wg.Add(2)
|
||||
|
||||
// Publish or update trixie snapshot
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
var resp *httptest.ResponseRecorder
|
||||
if iter == 0 {
|
||||
// First iteration: CREATE
|
||||
publishBody, _ := json.Marshal(gin.H{
|
||||
"SourceKind": "snapshot",
|
||||
"Distribution": "trixie",
|
||||
"Architectures": []string{"amd64"},
|
||||
"Sources": []gin.H{
|
||||
{"Name": snapshotTrixie},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"SkipBz2": true,
|
||||
"ForceOverwrite": true,
|
||||
"SkipCleanup": false, // Force cleanup to run
|
||||
})
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/publish/%s", sharedPrefix), publishBody)
|
||||
} else {
|
||||
// Subsequent iterations: UPDATE (this is what happens in production)
|
||||
updateBody, _ := json.Marshal(gin.H{
|
||||
"Snapshots": []gin.H{
|
||||
{"Component": "main", "Name": snapshotTrixie},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"SkipBz2": true,
|
||||
"ForceOverwrite": true,
|
||||
"SkipCleanup": false,
|
||||
})
|
||||
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/%s/trixie", sharedPrefix), updateBody)
|
||||
}
|
||||
trixiePublishCode = resp.Code
|
||||
c.Logf("[iter %d] Trixie publish/update completed: %d", iter, resp.Code)
|
||||
}()
|
||||
|
||||
// Publish or update bullseye snapshot
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
var resp *httptest.ResponseRecorder
|
||||
if iter == 0 {
|
||||
// First iteration: CREATE
|
||||
publishBody, _ := json.Marshal(gin.H{
|
||||
"SourceKind": "snapshot",
|
||||
"Distribution": "bullseye",
|
||||
"Architectures": []string{"amd64"},
|
||||
"Sources": []gin.H{
|
||||
{"Name": snapshotBullseye},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"SkipBz2": true,
|
||||
"ForceOverwrite": true,
|
||||
"SkipCleanup": false,
|
||||
})
|
||||
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/publish/%s", sharedPrefix), publishBody)
|
||||
} else {
|
||||
// Subsequent iterations: UPDATE
|
||||
updateBody, _ := json.Marshal(gin.H{
|
||||
"Snapshots": []gin.H{
|
||||
{"Component": "main", "Name": snapshotBullseye},
|
||||
},
|
||||
"Signing": gin.H{"Skip": true},
|
||||
"SkipBz2": true,
|
||||
"ForceOverwrite": true,
|
||||
"SkipCleanup": false,
|
||||
})
|
||||
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/%s/bullseye", sharedPrefix), updateBody)
|
||||
}
|
||||
bullseyePublishCode = resp.Code
|
||||
c.Logf("[iter %d] Bullseye publish/update completed: %d", iter, resp.Code)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Verify publishes succeeded (201 for create, 200 for update)
|
||||
expectedCode := 201
|
||||
if iter > 0 {
|
||||
expectedCode = 200
|
||||
}
|
||||
c.Assert(trixiePublishCode, Equals, expectedCode, Commentf("Trixie publish/update should succeed"))
|
||||
c.Assert(bullseyePublishCode, Equals, expectedCode, Commentf("Bullseye publish/update should succeed"))
|
||||
|
||||
// Verify ALL package files exist in the published pool
|
||||
publishedStorage := s.context.GetPublishedStorage("")
|
||||
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||
|
||||
missingFiles := []string{}
|
||||
expectedFiles := []string{}
|
||||
|
||||
// Check trixie packages
|
||||
for i := 0; i < numPackages; i++ {
|
||||
packageName := fmt.Sprintf("postgresql-17-trixie-pkg%d", i)
|
||||
version := fmt.Sprintf("17.0.%d", iter)
|
||||
|
||||
poolSubdir := string(packageName[0])
|
||||
expectedPath := filepath.Join(publicPath, sharedPrefix, "pool", "main", poolSubdir, packageName,
|
||||
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
|
||||
expectedFiles = append(expectedFiles, expectedPath)
|
||||
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||
missingFiles = append(missingFiles, fmt.Sprintf("TRIXIE: %s", filepath.Base(expectedPath)))
|
||||
}
|
||||
}
|
||||
|
||||
// Check bullseye packages
|
||||
for i := 0; i < numPackages; i++ {
|
||||
packageName := fmt.Sprintf("postgresql-17-bullseye-pkg%d", i)
|
||||
version := fmt.Sprintf("17.0.%d", iter)
|
||||
|
||||
poolSubdir := string(packageName[0])
|
||||
expectedPath := filepath.Join(publicPath, sharedPrefix, "pool", "main", poolSubdir, packageName,
|
||||
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||
|
||||
expectedFiles = append(expectedFiles, expectedPath)
|
||||
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||
missingFiles = append(missingFiles, fmt.Sprintf("BULLSEYE: %s", filepath.Base(expectedPath)))
|
||||
}
|
||||
}
|
||||
|
||||
// BUG: Files from one distribution are deleted by the other's cleanup
|
||||
if len(missingFiles) > 0 {
|
||||
c.Logf("★★★ BUG REPRODUCED in iteration %d/%d! ★★★", iter+1, numIterations)
|
||||
c.Logf("Both publishes to prefix '%s' succeeded, but %d files are MISSING:", sharedPrefix, len(missingFiles))
|
||||
for i, f := range missingFiles {
|
||||
c.Logf(" Missing file %d/%d: %s", i+1, len(missingFiles), f)
|
||||
}
|
||||
|
||||
c.Logf("\nThis reproduces the exact production bug where:")
|
||||
c.Logf(" 1. Mirror updates complete successfully")
|
||||
c.Logf(" 2. Snapshots are created")
|
||||
c.Logf(" 3. Both snapshots publish to same prefix (different distributions)")
|
||||
c.Logf(" 4. Cleanup from one publish DELETES files from the other")
|
||||
c.Logf(" 5. Result: apt-get returns 404 when downloading packages")
|
||||
|
||||
// List what's actually in the pool
|
||||
poolDir := filepath.Join(publicPath, sharedPrefix, "pool", "main")
|
||||
if entries, err := os.ReadDir(poolDir); err == nil {
|
||||
c.Logf("\nActual pool directory contents (%s):", poolDir)
|
||||
for _, entry := range entries {
|
||||
c.Logf(" - %s/", entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
c.Fatalf("BUG CONFIRMED (iteration %d/%d): %d files missing from shared pool",
|
||||
iter+1, numIterations, len(missingFiles))
|
||||
} else {
|
||||
c.Logf("[iter %d/%d] All %d files present - OK", iter+1, numIterations, len(expectedFiles))
|
||||
}
|
||||
}
|
||||
c.Logf("✓ All %d iterations passed - no files missing", numIterations)
|
||||
}
|
||||
+176
-77
@@ -24,7 +24,7 @@ import (
|
||||
// @Tags Repos
|
||||
// @Produce html
|
||||
// @Success 200 {object} string "HTML"
|
||||
// @Router /api/repos [get]
|
||||
// @Router /repos [get]
|
||||
func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
@@ -49,7 +49,7 @@ func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.H
|
||||
// @Param pkgPath path string true "Package Path" allowReserved=true
|
||||
// @Produce json
|
||||
// @Success 200 ""
|
||||
// @Router /api/{storage}/{pkgPath} [get]
|
||||
// @Router /repos/{storage}/{pkgPath} [get]
|
||||
func reposServeInAPIMode(c *gin.Context) {
|
||||
pkgpath := c.Param("pkgPath")
|
||||
|
||||
@@ -93,7 +93,7 @@ type repoCreateParams struct {
|
||||
DefaultDistribution string ` json:"DefaultDistribution" example:"stable"`
|
||||
// Default component when publishing from this local repo
|
||||
DefaultComponent string ` json:"DefaultComponent" example:"main"`
|
||||
// Snapshot name to create repoitory from (optional)
|
||||
// Snapshot name to create repository from (optional)
|
||||
FromSnapshot string ` json:"FromSnapshot" example:""`
|
||||
}
|
||||
|
||||
@@ -122,46 +122,62 @@ func apiReposCreate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
repo := deb.NewLocalRepo(b.Name, b.Comment)
|
||||
repo.DefaultComponent = b.DefaultComponent
|
||||
repo.DefaultDistribution = b.DefaultDistribution
|
||||
|
||||
// Handler: Pre-task validations (shallow)
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
|
||||
var resources []string
|
||||
if b.FromSnapshot != "" {
|
||||
var snapshot *deb.Snapshot
|
||||
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
|
||||
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
|
||||
snapshot, err := collectionFactory.SnapshotCollection().ByName(b.FromSnapshot)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err))
|
||||
return
|
||||
}
|
||||
resources = append(resources, string(snapshot.Key()))
|
||||
}
|
||||
|
||||
err = snapshotCollection.LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to load source snapshot: %s", err))
|
||||
return
|
||||
taskName := fmt.Sprintf("Create repository %s", b.Name)
|
||||
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
// Task: Create fresh collection and check/create ATOMIC inside task
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
|
||||
// Check duplicate inside lock
|
||||
if _, err := taskCollection.ByName(b.Name); err == nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil},
|
||||
fmt.Errorf("local repo with name %s already exists", b.Name)
|
||||
}
|
||||
|
||||
repo.UpdateRefList(snapshot.RefList())
|
||||
}
|
||||
// Create repo
|
||||
repo := deb.NewLocalRepo(b.Name, b.Comment)
|
||||
repo.DefaultComponent = b.DefaultComponent
|
||||
repo.DefaultDistribution = b.DefaultDistribution
|
||||
|
||||
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||
if b.FromSnapshot != "" {
|
||||
snapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
|
||||
if _, err := localRepoCollection.ByName(b.Name); err == nil {
|
||||
AbortWithJSONError(c, http.StatusConflict, fmt.Errorf("local repo with name %s already exists", b.Name))
|
||||
return
|
||||
}
|
||||
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil},
|
||||
fmt.Errorf("source snapshot not found: %s", err)
|
||||
}
|
||||
|
||||
err := localRepoCollection.Add(repo)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
err = snapshotCollection.LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil},
|
||||
fmt.Errorf("unable to load source snapshot: %s", err)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, repo)
|
||||
repo.UpdateRefList(snapshot.RefList())
|
||||
}
|
||||
|
||||
err := taskCollection.Add(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: repo}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type reposEditParams struct {
|
||||
@@ -171,7 +187,7 @@ type reposEditParams struct {
|
||||
Comment *string ` json:"Comment" example:"example repo"`
|
||||
// Change Default Distribution for publishing
|
||||
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
|
||||
// Change Devault Component for publishing
|
||||
// Change Default Component for publishing
|
||||
DefaultComponent *string ` json:"DefaultComponent" example:""`
|
||||
}
|
||||
|
||||
@@ -192,41 +208,66 @@ func apiReposEdit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Load shallowly for 404 check and resource key.
|
||||
// Mutation and duplicate check happen inside the task for atomicity.
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.LocalRepoCollection()
|
||||
|
||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
||||
name := c.Params.ByName("name")
|
||||
repo, err := collection.ByName(name)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 404, err)
|
||||
return
|
||||
}
|
||||
|
||||
if b.Name != nil {
|
||||
_, err := collection.ByName(*b.Name)
|
||||
if err == nil {
|
||||
// already exists
|
||||
AbortWithJSONError(c, 404, err)
|
||||
if b.Name != nil && *b.Name != name {
|
||||
if _, err = collection.ByName(*b.Name); err == nil {
|
||||
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: local repo %q already exists", *b.Name))
|
||||
return
|
||||
}
|
||||
repo.Name = *b.Name
|
||||
}
|
||||
if b.Comment != nil {
|
||||
repo.Comment = *b.Comment
|
||||
}
|
||||
if b.DefaultDistribution != nil {
|
||||
repo.DefaultDistribution = *b.DefaultDistribution
|
||||
}
|
||||
if b.DefaultComponent != nil {
|
||||
repo.DefaultComponent = *b.DefaultComponent
|
||||
}
|
||||
|
||||
err = collection.Update(repo)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 500, err)
|
||||
return
|
||||
}
|
||||
resources := []string{string(repo.Key())}
|
||||
taskName := fmt.Sprintf("Edit repository %s", name)
|
||||
|
||||
c.JSON(200, repo)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
// Task: Create fresh collection inside task after lock
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
|
||||
// Fresh load after lock acquired
|
||||
repo, err := taskCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
|
||||
}
|
||||
|
||||
// Check and update ATOMIC (inside lock)
|
||||
if b.Name != nil && *b.Name != name {
|
||||
_, err := taskCollection.ByName(*b.Name)
|
||||
if err == nil {
|
||||
// already exists
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil},
|
||||
fmt.Errorf("local repo with name %q already exists", *b.Name)
|
||||
}
|
||||
repo.Name = *b.Name
|
||||
}
|
||||
if b.Comment != nil {
|
||||
repo.Comment = *b.Comment
|
||||
}
|
||||
if b.DefaultDistribution != nil {
|
||||
repo.DefaultDistribution = *b.DefaultDistribution
|
||||
}
|
||||
if b.DefaultComponent != nil {
|
||||
repo.DefaultComponent = *b.DefaultComponent
|
||||
}
|
||||
|
||||
err = taskCollection.Update(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: repo}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/repos/:name
|
||||
@@ -268,10 +309,10 @@ func apiReposDrop(c *gin.Context) {
|
||||
force := c.Request.URL.Query().Get("force") == "1"
|
||||
name := c.Params.ByName("name")
|
||||
|
||||
// Load shallowly for 404 check, resource key, and task name.
|
||||
// Full checks (published/snapshots) happen inside the task.
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.LocalRepoCollection()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
publishedCollection := collectionFactory.PublishedRepoCollection()
|
||||
|
||||
repo, err := collection.ByName(name)
|
||||
if err != nil {
|
||||
@@ -282,19 +323,32 @@ func apiReposDrop(c *gin.Context) {
|
||||
resources := []string{string(repo.Key())}
|
||||
taskName := fmt.Sprintf("Delete repo %s", name)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
published := publishedCollection.ByLocalRepo(repo)
|
||||
// Task: Create fresh collections inside task after lock acquired
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
taskPublishedCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||
|
||||
// Re-read repo with fresh collection after lock
|
||||
repo, err := taskCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
// Check with fresh collections
|
||||
published := taskPublishedCollection.ByLocalRepo(repo)
|
||||
if len(published) > 0 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published")
|
||||
}
|
||||
|
||||
if !force {
|
||||
snapshots := snapshotCollection.ByLocalRepoSource(repo)
|
||||
snapshots := taskSnapshotCollection.ByLocalRepoSource(repo)
|
||||
if len(snapshots) > 0 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo has snapshots, use ?force=1 to override")
|
||||
}
|
||||
}
|
||||
|
||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, collection.Drop(repo)
|
||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, taskCollection.Drop(repo)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -351,10 +405,13 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
||||
return
|
||||
}
|
||||
|
||||
// Load shallowly for 404 check and resource key.
|
||||
// Full load and mutations happen inside the task.
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.LocalRepoCollection()
|
||||
|
||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
||||
name := c.Params.ByName("name")
|
||||
repo, err := collection.ByName(name)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 404, err)
|
||||
return
|
||||
@@ -363,13 +420,23 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
||||
resources := []string{string(repo.Key())}
|
||||
|
||||
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err = collection.LoadComplete(repo)
|
||||
// Task: Create fresh factory and collection inside task after lock
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
|
||||
// Fresh load after lock acquired (use captured `name` variable, not gin context)
|
||||
repo, err := taskCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
|
||||
}
|
||||
|
||||
err = taskCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
out.Printf("Loading packages...\n")
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), taskCollectionFactory.PackageCollection(), nil)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -378,7 +445,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
||||
for _, ref := range b.PackageRefs {
|
||||
var p *deb.Package
|
||||
|
||||
p, err = collectionFactory.PackageCollection().ByKey([]byte(ref))
|
||||
p, err = taskCollectionFactory.PackageCollection().ByKey([]byte(ref))
|
||||
if err != nil {
|
||||
if err == database.ErrNotFound {
|
||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("packages %s: %s", ref, err)
|
||||
@@ -394,7 +461,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
||||
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
|
||||
err = collectionFactory.LocalRepoCollection().Update(repo)
|
||||
err = taskCollection.Update(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
@@ -410,6 +477,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
||||
// @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
|
||||
// @Param name path string true "Repository name"
|
||||
// @Consume json
|
||||
// @Param request body reposPackagesAddDeleteParams true "Parameters"
|
||||
// @Param _async query bool false "Run in background and return task object"
|
||||
// @Produce json
|
||||
@@ -455,7 +523,7 @@ func apiReposPackagesDelete(c *gin.Context) {
|
||||
// @Tags Repos
|
||||
// @Param name path string true "Repository name"
|
||||
// @Param dir path string true "Directory of packages"
|
||||
// @Param file path string false "Filename (optional)"
|
||||
// @Param file path string true "Filename"
|
||||
// @Param _async query bool false "Run in background and return task object"
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "OK"
|
||||
@@ -500,6 +568,8 @@ func apiReposPackageFromDir(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Load shallowly for 404 check and resource key.
|
||||
// Full load and mutations happen inside the task.
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.LocalRepoCollection()
|
||||
|
||||
@@ -523,7 +593,17 @@ func apiReposPackageFromDir(c *gin.Context) {
|
||||
resources := []string{string(repo.Key())}
|
||||
resources = append(resources, sources...)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err = collection.LoadComplete(repo)
|
||||
// Task: Create fresh factory and collection inside task after lock
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
|
||||
// Fresh load after lock acquired
|
||||
repo, err := taskCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
err = taskCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -544,13 +624,13 @@ func apiReposPackageFromDir(c *gin.Context) {
|
||||
|
||||
packageFiles, otherFiles, failedFiles = deb.CollectPackageFiles(sources, reporter)
|
||||
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
||||
list, err = deb.NewPackageListFromRefList(repo.RefList(), taskCollectionFactory.PackageCollection(), nil)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
|
||||
collectionFactory.PackageCollection(), reporter, nil, collectionFactory.ChecksumCollection)
|
||||
taskCollectionFactory.PackageCollection(), reporter, nil, taskCollectionFactory.ChecksumCollection)
|
||||
failedFiles = append(failedFiles, failedFiles2...)
|
||||
processedFiles = append(processedFiles, otherFiles...)
|
||||
|
||||
@@ -560,7 +640,7 @@ func apiReposPackageFromDir(c *gin.Context) {
|
||||
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
|
||||
err = collectionFactory.LocalRepoCollection().Update(repo)
|
||||
err = taskCollection.Update(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
@@ -613,11 +693,11 @@ type reposCopyPackageParams struct {
|
||||
// @Summary Copy Package
|
||||
// @Description Copies a package from a source to destination repository
|
||||
// @Tags Repos
|
||||
// @Produce json
|
||||
// @Param name path string true "Destination repo"
|
||||
// @Param src path string true "Source repo"
|
||||
// @Param file path string true "File/packages to copy"
|
||||
// @Param _async query bool false "Run in background and return task object"
|
||||
// @Produce json
|
||||
// @Success 200 {object} task.ProcessReturnValue "msg"
|
||||
// @Failure 400 {object} Error "Bad Request"
|
||||
// @Failure 404 {object} Error "Not Found"
|
||||
@@ -639,6 +719,8 @@ func apiReposCopyPackage(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Load shallowly for 404 check and resource keys.
|
||||
// Full load and mutations happen inside the task.
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
|
||||
if err != nil {
|
||||
@@ -662,12 +744,26 @@ func apiReposCopyPackage(c *gin.Context) {
|
||||
resources := []string{string(dstRepo.Key()), string(srcRepo.Key())}
|
||||
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
|
||||
// Task: Create fresh factory and collections inside task after lock
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
|
||||
// Fresh load of both repos after lock acquired
|
||||
dstRepo, err := taskCollectionFactory.LocalRepoCollection().ByName(dstRepoName)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
|
||||
}
|
||||
|
||||
err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
|
||||
srcRepo, err := taskCollectionFactory.LocalRepoCollection().ByName(srcRepoName)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
|
||||
}
|
||||
|
||||
err = taskCollectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
|
||||
}
|
||||
|
||||
err = taskCollectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
|
||||
}
|
||||
@@ -680,12 +776,12 @@ func apiReposCopyPackage(c *gin.Context) {
|
||||
RemovedLines: []string{},
|
||||
}
|
||||
|
||||
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
||||
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err)
|
||||
}
|
||||
|
||||
srcList, err := deb.NewPackageListFromRefList(srcRefList, collectionFactory.PackageCollection(), context.Progress())
|
||||
srcList, err := deb.NewPackageListFromRefList(srcRefList, taskCollectionFactory.PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
|
||||
}
|
||||
@@ -753,7 +849,7 @@ func apiReposCopyPackage(c *gin.Context) {
|
||||
} else {
|
||||
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
|
||||
|
||||
err = collectionFactory.LocalRepoCollection().Update(dstRepo)
|
||||
err = taskCollectionFactory.LocalRepoCollection().Update(dstRepo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
@@ -856,6 +952,9 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
||||
resources = append(resources, sources...)
|
||||
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
// Task: Create fresh factory and collection inside task after lock
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
|
||||
var (
|
||||
err error
|
||||
verifier = context.GetVerifier()
|
||||
@@ -871,8 +970,8 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
||||
changesFiles, failedFiles = deb.CollectChangesFiles(sources, reporter)
|
||||
_, failedFiles2, err = deb.ImportChangesFiles(
|
||||
changesFiles, reporter, acceptUnsigned, ignoreSignature, forceReplace, noRemoveFiles, verifier,
|
||||
repoTemplate, context.Progress(), collectionFactory.LocalRepoCollection(), collectionFactory.PackageCollection(),
|
||||
context.PackagePool(), collectionFactory.ChecksumCollection, nil, query.Parse)
|
||||
repoTemplate, context.Progress(), taskCollectionFactory.LocalRepoCollection(), taskCollectionFactory.PackageCollection(),
|
||||
context.PackagePool(), taskCollectionFactory.ChecksumCollection, nil, query.Parse)
|
||||
failedFiles = append(failedFiles, failedFiles2...)
|
||||
|
||||
if err != nil {
|
||||
@@ -901,10 +1000,10 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
||||
out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", "))
|
||||
}
|
||||
|
||||
ret := reposIncludePackageFromDirResponse{
|
||||
ret := reposIncludePackageFromDirResponse{
|
||||
Report: reporter,
|
||||
FailedFiles: failedFiles,
|
||||
}
|
||||
}
|
||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: ret}, nil
|
||||
})
|
||||
}
|
||||
|
||||
+32
-26
@@ -11,13 +11,19 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/aptly-dev/aptly/docs"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
// _ "github.com/aptly-dev/aptly/docs" // import docs
|
||||
// swaggerFiles "github.com/swaggo/files"
|
||||
// ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
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 {
|
||||
return func(c *gin.Context) {
|
||||
countPackagesByRepos()
|
||||
@@ -25,21 +31,21 @@ func apiMetricsGet() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func redirectSwagger(c *gin.Context) {
|
||||
if c.Request.URL.Path == "/docs/index.html" {
|
||||
c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||
return
|
||||
}
|
||||
if c.Request.URL.Path == "/docs/" {
|
||||
c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||
return
|
||||
}
|
||||
if c.Request.URL.Path == "/docs" {
|
||||
c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
// func redirectSwagger(c *gin.Context) {
|
||||
// if c.Request.URL.Path == "/docs/index.html" {
|
||||
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||
// return
|
||||
// }
|
||||
// if c.Request.URL.Path == "/docs/" {
|
||||
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||
// return
|
||||
// }
|
||||
// if c.Request.URL.Path == "/docs" {
|
||||
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||
// return
|
||||
// }
|
||||
// c.Next()
|
||||
// }
|
||||
|
||||
// Router returns prebuilt with routes http.Handler
|
||||
func Router(c *ctx.AptlyContext) http.Handler {
|
||||
@@ -63,14 +69,14 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||
|
||||
router.Use(gin.Recovery(), gin.ErrorLogger())
|
||||
|
||||
if c.Config().EnableSwaggerEndpoint {
|
||||
router.GET("docs.html", func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||
})
|
||||
router.Use(redirectSwagger)
|
||||
url := ginSwagger.URL("/docs/doc.json")
|
||||
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||
}
|
||||
// if c.Config().EnableSwaggerEndpoint {
|
||||
// router.GET("docs.html", func(c *gin.Context) {
|
||||
// c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||
// })
|
||||
// router.Use(redirectSwagger)
|
||||
// url := ginSwagger.URL("/docs/doc.json")
|
||||
// router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||
// }
|
||||
|
||||
if c.Config().EnableMetricsEndpoint {
|
||||
MetricsCollectorRegistrar.Register(router)
|
||||
|
||||
+162
-64
@@ -74,26 +74,33 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
|
||||
}
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.RemoteRepoCollection()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
name := c.Params.ByName("name")
|
||||
|
||||
repo, err = collection.ByName(name)
|
||||
repo, err = collectionFactory.RemoteRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// including snapshot resource key
|
||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
||||
resources := []string{string(repo.Key())}
|
||||
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err := repo.CheckLock()
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
|
||||
repo, err := taskMirrorCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
|
||||
}
|
||||
|
||||
err = collection.LoadComplete(repo)
|
||||
err = taskMirrorCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -107,7 +114,7 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
|
||||
snapshot.Description = b.Description
|
||||
}
|
||||
|
||||
err = snapshotCollection.Add(snapshot)
|
||||
err = taskSnapshotCollection.Add(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||
}
|
||||
@@ -156,6 +163,7 @@ func apiSnapshotsCreate(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: Pre-task validation (shallow load for 404 checks only)
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
var resources []string
|
||||
@@ -169,37 +177,62 @@ func apiSnapshotsCreate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources = append(resources, string(sources[i].ResourceKey()))
|
||||
resources = append(resources, string(sources[i].Key()))
|
||||
}
|
||||
|
||||
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
for i := range sources {
|
||||
err = snapshotCollection.LoadComplete(sources[i])
|
||||
// Phase 2: Inside task lock - create fresh factory
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
taskPackageCollection := taskCollectionFactory.PackageCollection()
|
||||
|
||||
// Fresh load of all sources after lock acquired
|
||||
freshSources := make([]*deb.Snapshot, len(b.SourceSnapshots))
|
||||
for i := range b.SourceSnapshots {
|
||||
freshSources[i], err = taskSnapshotCollection.ByName(b.SourceSnapshots[i])
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
// LoadComplete on fresh copy
|
||||
err = taskSnapshotCollection.LoadComplete(freshSources[i])
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
}
|
||||
|
||||
list := deb.NewPackageList()
|
||||
// Merge packages from all source snapshots
|
||||
var refList *deb.PackageRefList
|
||||
if len(freshSources) > 0 {
|
||||
refList = freshSources[0].RefList()
|
||||
for i := 1; i < len(freshSources); i++ {
|
||||
refList = refList.Merge(freshSources[i].RefList(), true, false)
|
||||
}
|
||||
} else {
|
||||
refList = deb.NewPackageRefList()
|
||||
}
|
||||
|
||||
// verify package refs and build package list
|
||||
for _, ref := range b.PackageRefs {
|
||||
p, err := collectionFactory.PackageCollection().ByKey([]byte(ref))
|
||||
if err != nil {
|
||||
if err == database.ErrNotFound {
|
||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err)
|
||||
// Add any explicitly specified package refs on top
|
||||
if len(b.PackageRefs) > 0 {
|
||||
list := deb.NewPackageList()
|
||||
for _, ref := range b.PackageRefs {
|
||||
p, err := taskPackageCollection.ByKey([]byte(ref))
|
||||
if err != nil {
|
||||
if err == database.ErrNotFound {
|
||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err)
|
||||
}
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
err = list.Add(p)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||
}
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
err = list.Add(p)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||
}
|
||||
refList = refList.Merge(deb.NewPackageRefListFromPackageList(list), true, false)
|
||||
}
|
||||
|
||||
snapshot = deb.NewSnapshotFromRefList(b.Name, sources, deb.NewPackageRefListFromPackageList(list), b.Description)
|
||||
snapshot = deb.NewSnapshotFromRefList(b.Name, freshSources, refList, b.Description)
|
||||
|
||||
err = snapshotCollection.Add(snapshot)
|
||||
err = taskSnapshotCollection.Add(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||
}
|
||||
@@ -217,10 +250,9 @@ type snapshotsCreateFromRepositoryParams struct {
|
||||
// @Summary Snapshot Repository
|
||||
// @Description **Create a snapshot of a repository by name**
|
||||
// @Tags Snapshots
|
||||
// @Param name path string true "Repository name"
|
||||
// @Consume json
|
||||
// @Param request body snapshotsCreateFromRepositoryParams true "Parameters"
|
||||
// @Param name path string true "Name of the snapshot"
|
||||
// @Param name path string true "Repository name"
|
||||
// @Param _async query bool false "Run in background and return task object"
|
||||
// @Produce json
|
||||
// @Success 201 {object} deb.Snapshot "Created snapshot object"
|
||||
@@ -241,21 +273,28 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
|
||||
}
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.LocalRepoCollection()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
name := c.Params.ByName("name")
|
||||
|
||||
repo, err = collection.ByName(name)
|
||||
repo, err = collectionFactory.LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// including snapshot resource key
|
||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
||||
resources := []string{string(repo.Key())}
|
||||
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err := collection.LoadComplete(repo)
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskRepoCollection := taskCollectionFactory.LocalRepoCollection()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
|
||||
repo, err := taskRepoCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
err = taskRepoCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -269,7 +308,7 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
|
||||
snapshot.Description = b.Description
|
||||
}
|
||||
|
||||
err = snapshotCollection.Add(snapshot)
|
||||
err = taskSnapshotCollection.Add(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||
}
|
||||
@@ -307,6 +346,7 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Phase 1: Pre-task validation (shallow load for 404 check only)
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.SnapshotCollection()
|
||||
name := c.Params.ByName("name")
|
||||
@@ -317,14 +357,38 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources := []string{string(snapshot.ResourceKey()), "S" + b.Name}
|
||||
taskName := fmt.Sprintf("Update snapshot %s", name)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
_, err := collection.ByName(b.Name)
|
||||
// Pre-task validation of new name if provided (skip if renaming to same name)
|
||||
if b.Name != "" && b.Name != name {
|
||||
_, err = collection.ByName(b.Name)
|
||||
if err == nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
|
||||
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resources := []string{string(snapshot.Key())}
|
||||
taskName := fmt.Sprintf("Update snapshot %s", name)
|
||||
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
// Phase 2: Inside task lock - create fresh factory
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskCollection := taskCollectionFactory.SnapshotCollection()
|
||||
|
||||
// Fresh load after lock acquired
|
||||
snapshot, err = taskCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
// Fresh duplicate check inside lock
|
||||
if b.Name != "" {
|
||||
_, err := taskCollection.ByName(b.Name)
|
||||
if err == nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update fresh copy
|
||||
if b.Name != "" {
|
||||
snapshot.Name = b.Name
|
||||
}
|
||||
@@ -333,7 +397,7 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
||||
snapshot.Description = b.Description
|
||||
}
|
||||
|
||||
err = collectionFactory.SnapshotCollection().Update(snapshot)
|
||||
err = taskCollection.Update(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -387,9 +451,9 @@ func apiSnapshotsDrop(c *gin.Context) {
|
||||
name := c.Params.ByName("name")
|
||||
force := c.Request.URL.Query().Get("force") == "1"
|
||||
|
||||
// Phase 1: Pre-task validation (shallow load for 404 check only)
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
publishedCollection := collectionFactory.PublishedRepoCollection()
|
||||
|
||||
snapshot, err := snapshotCollection.ByName(name)
|
||||
if err != nil {
|
||||
@@ -397,23 +461,37 @@ func apiSnapshotsDrop(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources := []string{string(snapshot.ResourceKey())}
|
||||
resources := []string{string(snapshot.Key())}
|
||||
taskName := fmt.Sprintf("Delete snapshot %s", name)
|
||||
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
published := publishedCollection.BySnapshot(snapshot)
|
||||
// Phase 2: Inside task lock - create fresh collections
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
taskPublishedCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||
|
||||
// Fresh load after lock acquired
|
||||
snapshot, err := taskSnapshotCollection.ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
// Fresh checks with current collections
|
||||
published := taskPublishedCollection.BySnapshot(snapshot)
|
||||
|
||||
if len(published) > 0 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: snapshot is published")
|
||||
}
|
||||
|
||||
if !force {
|
||||
snapshots := snapshotCollection.BySnapshotSource(snapshot)
|
||||
// Using fresh collection for dependency check
|
||||
snapshots := taskSnapshotCollection.BySnapshotSource(snapshot)
|
||||
if len(snapshots) > 0 {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override")
|
||||
}
|
||||
}
|
||||
|
||||
err = snapshotCollection.Drop(snapshot)
|
||||
err = taskSnapshotCollection.Drop(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -568,6 +646,7 @@ func apiSnapshotsMerge(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Phase 1: Pre-task validation (shallow load for 404 checks only)
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||
|
||||
@@ -580,36 +659,47 @@ func apiSnapshotsMerge(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources[i] = string(sources[i].ResourceKey())
|
||||
resources[i] = string(sources[i].Key())
|
||||
}
|
||||
|
||||
maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err = snapshotCollection.LoadComplete(sources[0])
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
result := sources[0].RefList()
|
||||
for i := 1; i < len(sources); i++ {
|
||||
err = snapshotCollection.LoadComplete(sources[i])
|
||||
// Phase 2: Inside task lock - create fresh factory
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||
|
||||
// Fresh load of all sources inside task
|
||||
freshSources := make([]*deb.Snapshot, len(body.Sources))
|
||||
for i := range body.Sources {
|
||||
freshSources[i], err = taskSnapshotCollection.ByName(body.Sources[i])
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
result = result.Merge(sources[i].RefList(), overrideMatching, false)
|
||||
// LoadComplete on fresh copy
|
||||
err = taskSnapshotCollection.LoadComplete(freshSources[i])
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Merge using fresh sources
|
||||
result := freshSources[0].RefList()
|
||||
for i := 1; i < len(freshSources); i++ {
|
||||
result = result.Merge(freshSources[i].RefList(), overrideMatching, false)
|
||||
}
|
||||
|
||||
if latest {
|
||||
result.FilterLatestRefs()
|
||||
}
|
||||
|
||||
sourceDescription := make([]string, len(sources))
|
||||
for i, s := range sources {
|
||||
sourceDescription := make([]string, len(freshSources))
|
||||
for i, s := range freshSources {
|
||||
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
|
||||
}
|
||||
|
||||
snapshot = deb.NewSnapshotFromRefList(name, sources, result,
|
||||
snapshot = deb.NewSnapshotFromRefList(name, freshSources, result,
|
||||
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
|
||||
|
||||
err = collectionFactory.SnapshotCollection().Add(snapshot)
|
||||
err = taskCollectionFactory.SnapshotCollection().Add(snapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
@@ -690,24 +780,32 @@ func apiSnapshotsPull(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())}
|
||||
resources := []string{string(sourceSnapshot.Key()), string(toSnapshot.Key())}
|
||||
taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot)
|
||||
// Phase 2: Inside task lock - create fresh factory
|
||||
taskCollectionFactory := context.NewCollectionFactory()
|
||||
|
||||
// Fresh load of snapshots after lock acquired
|
||||
freshToSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot)
|
||||
freshSourceSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(body.Source)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
err = taskCollectionFactory.SnapshotCollection().LoadComplete(freshSourceSnapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
// convert snapshots to package list
|
||||
toPackageList, err := deb.NewPackageListFromRefList(toSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
||||
toPackageList, err := deb.NewPackageListFromRefList(freshToSnapshot.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
sourcePackageList, err := deb.NewPackageListFromRefList(sourceSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
||||
sourcePackageList, err := deb.NewPackageListFromRefList(freshSourceSnapshot.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
@@ -804,10 +902,10 @@ func apiSnapshotsPull(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Create <destination> snapshot
|
||||
destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{toSnapshot, sourceSnapshot}, toPackageList,
|
||||
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", toSnapshot.Name, sourceSnapshot.Name, strings.Join(body.Queries, ", ")))
|
||||
destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{freshToSnapshot, freshSourceSnapshot}, toPackageList,
|
||||
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", freshToSnapshot.Name, freshSourceSnapshot.Name, strings.Join(body.Queries, ", ")))
|
||||
|
||||
err = collectionFactory.SnapshotCollection().Add(destinationSnapshot)
|
||||
err = taskCollectionFactory.SnapshotCollection().Add(destinationSnapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
+41
-48
@@ -5,35 +5,28 @@ package azure
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
)
|
||||
|
||||
func isBlobNotFound(err error) bool {
|
||||
var respErr *azcore.ResponseError
|
||||
if errors.As(err, &respErr) {
|
||||
return respErr.StatusCode == 404 // BlobNotFound
|
||||
}
|
||||
return false
|
||||
storageError, ok := err.(azblob.StorageError)
|
||||
return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
|
||||
}
|
||||
|
||||
type azContext struct {
|
||||
client *azblob.Client
|
||||
container string
|
||||
container azblob.ContainerURL
|
||||
prefix string
|
||||
}
|
||||
|
||||
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
|
||||
cred, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -42,14 +35,15 @@ func newAzContext(accountName, accountKey, container, prefix, endpoint string) (
|
||||
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
|
||||
}
|
||||
|
||||
serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil)
|
||||
url, err := url.Parse(fmt.Sprintf("%s/%s", endpoint, container))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
|
||||
|
||||
result := &azContext{
|
||||
client: serviceClient,
|
||||
container: container,
|
||||
container: containerURL,
|
||||
prefix: prefix,
|
||||
}
|
||||
|
||||
@@ -60,6 +54,10 @@ func (az *azContext) blobPath(path string) string {
|
||||
return filepath.Join(az.prefix, path)
|
||||
}
|
||||
|
||||
func (az *azContext) blobURL(path string) azblob.BlobURL {
|
||||
return az.container.NewBlobURL(az.blobPath(path))
|
||||
}
|
||||
|
||||
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
|
||||
const delimiter = "/"
|
||||
paths = make([]string, 0, 1024)
|
||||
@@ -69,33 +67,27 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
||||
prefix += delimiter
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
maxResults := int32(1)
|
||||
pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{
|
||||
Prefix: &prefix,
|
||||
MaxResults: &maxResults,
|
||||
Include: azblob.ListBlobsInclude{Metadata: true},
|
||||
})
|
||||
|
||||
// Iterate over each page
|
||||
for pager.More() {
|
||||
page, err := pager.NextPage(ctx)
|
||||
for marker := (azblob.Marker{}); marker.NotDone(); {
|
||||
listBlob, err := az.container.ListBlobsFlatSegment(
|
||||
context.Background(), marker, azblob.ListBlobsSegmentOptions{
|
||||
Prefix: prefix,
|
||||
MaxResults: 1,
|
||||
Details: azblob.BlobListingDetails{Metadata: true}})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
|
||||
}
|
||||
|
||||
for _, blob := range page.Segment.BlobItems {
|
||||
if prefix == "" {
|
||||
paths = append(paths, *blob.Name)
|
||||
} else {
|
||||
name := *blob.Name
|
||||
paths = append(paths, name[len(prefix):])
|
||||
}
|
||||
b := *blob
|
||||
md5 := b.Properties.ContentMD5
|
||||
md5s = append(md5s, fmt.Sprintf("%x", md5))
|
||||
marker = listBlob.NextMarker
|
||||
|
||||
for _, blob := range listBlob.Segment.BlobItems {
|
||||
if prefix == "" {
|
||||
paths = append(paths, blob.Name)
|
||||
} else {
|
||||
paths = append(paths, blob.Name[len(prefix):])
|
||||
}
|
||||
md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||
progress.AddBar(1)
|
||||
@@ -105,27 +97,28 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
||||
return paths, md5s, nil
|
||||
}
|
||||
|
||||
func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error {
|
||||
uploadOptions := &azblob.UploadFileOptions{
|
||||
BlockSize: 4 * 1024 * 1024,
|
||||
Concurrency: 8,
|
||||
func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
|
||||
uploadOptions := azblob.UploadStreamToBlockBlobOptions{
|
||||
BufferSize: 4 * 1024 * 1024,
|
||||
MaxBuffers: 8,
|
||||
}
|
||||
|
||||
path := az.blobPath(blobName)
|
||||
if len(sourceMD5) > 0 {
|
||||
decodedMD5, err := hex.DecodeString(sourceMD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadOptions.HTTPHeaders = &blob.HTTPHeaders{
|
||||
BlobContentMD5: decodedMD5,
|
||||
uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
|
||||
ContentMD5: decodedMD5,
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if file, ok := source.(*os.File); ok {
|
||||
_, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions)
|
||||
}
|
||||
_, err := azblob.UploadStreamToBlockBlob(
|
||||
context.Background(),
|
||||
source,
|
||||
blob.ToBlockBlobURL(),
|
||||
uploadOptions,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
+24
-22
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/pkg/errors"
|
||||
@@ -40,7 +41,10 @@ func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.Checksu
|
||||
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
|
||||
}
|
||||
|
||||
func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) {
|
||||
func (pool *PackagePool) ensureChecksums(
|
||||
poolPath string,
|
||||
checksumStorage aptly.ChecksumStorage,
|
||||
) (*utils.ChecksumInfo, error) {
|
||||
targetChecksums, err := checksumStorage.Get(poolPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -48,7 +52,8 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
||||
|
||||
if targetChecksums == nil {
|
||||
// we don't have checksums stored yet for this file
|
||||
download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil)
|
||||
blob := pool.az.blobURL(poolPath)
|
||||
download, err := blob.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
if isBlobNotFound(err) {
|
||||
return nil, nil
|
||||
@@ -58,7 +63,7 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
||||
}
|
||||
|
||||
targetChecksums = &utils.ChecksumInfo{}
|
||||
*targetChecksums, err = utils.ChecksumsForReader(download.Body)
|
||||
*targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
|
||||
}
|
||||
@@ -87,49 +92,45 @@ func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, er
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Size(path string) (int64, error) {
|
||||
serviceClient := pool.az.client.ServiceClient()
|
||||
containerClient := serviceClient.NewContainerClient(pool.az.container)
|
||||
blobClient := containerClient.NewBlobClient(path)
|
||||
|
||||
props, err := blobClient.GetProperties(context.TODO(), nil)
|
||||
blob := pool.az.blobURL(path)
|
||||
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||
}
|
||||
|
||||
return *props.ContentLength, nil
|
||||
return props.ContentLength(), nil
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
||||
blob := pool.az.blobURL(path)
|
||||
|
||||
temp, err := os.CreateTemp("", "blob-download")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
|
||||
return nil, errors.Wrap(err, "error creating temporary file for blob download")
|
||||
}
|
||||
defer func () { _ = os.Remove(temp.Name()) }()
|
||||
defer func() { _ = os.Remove(temp.Name()) }()
|
||||
|
||||
_, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
|
||||
err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error downloading blob %s", path)
|
||||
return nil, errors.Wrapf(err, "error downloading blob at %s", path)
|
||||
}
|
||||
|
||||
return temp, nil
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Remove(path string) (int64, error) {
|
||||
serviceClient := pool.az.client.ServiceClient()
|
||||
containerClient := serviceClient.NewContainerClient(pool.az.container)
|
||||
blobClient := containerClient.NewBlobClient(path)
|
||||
|
||||
props, err := blobClient.GetProperties(context.TODO(), nil)
|
||||
blob := pool.az.blobURL(path)
|
||||
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||
return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
|
||||
}
|
||||
|
||||
_, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil)
|
||||
_, err = blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
|
||||
}
|
||||
|
||||
return *props.ContentLength, nil
|
||||
return props.ContentLength(), nil
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
|
||||
@@ -143,6 +144,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||
}
|
||||
|
||||
path := pool.buildPoolPath(basename, checksums)
|
||||
blob := pool.az.blobURL(path)
|
||||
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -158,7 +160,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||
}
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
err = pool.az.putFile(path, source, checksums.MD5)
|
||||
err = pool.az.putFile(blob, source, checksums.MD5)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/files"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
@@ -50,10 +50,8 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
|
||||
|
||||
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||
c.Assert(err, IsNil)
|
||||
publicAccessType := azblob.PublicAccessTypeContainer
|
||||
_, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
|
||||
Access: &publicAccessType,
|
||||
})
|
||||
cnt := s.pool.az.container
|
||||
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||
|
||||
+57
-70
@@ -3,22 +3,19 @@ package azure
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease"
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
||||
type PublishedStorage struct {
|
||||
// FIXME: unused ???? prefix string
|
||||
az *azContext
|
||||
pathCache map[string]map[string]string
|
||||
}
|
||||
@@ -67,7 +64,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||
}
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
err = storage.az.putFile(path, source, sourceMD5)
|
||||
err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
||||
}
|
||||
@@ -77,15 +74,14 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||
|
||||
// RemoveDirs removes directory structure under public path
|
||||
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
|
||||
path = storage.az.blobPath(path)
|
||||
filelist, err := storage.Filelist(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, filename := range filelist {
|
||||
blob := filepath.Join(path, filename)
|
||||
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil)
|
||||
blob := storage.az.blobURL(filepath.Join(path, filename))
|
||||
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
||||
}
|
||||
@@ -96,8 +92,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error
|
||||
|
||||
// Remove removes single file under public path
|
||||
func (storage *PublishedStorage) Remove(path string) error {
|
||||
path = storage.az.blobPath(path)
|
||||
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil)
|
||||
blob := storage.az.blobURL(path)
|
||||
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
||||
}
|
||||
@@ -116,8 +112,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
||||
|
||||
relFilePath := filepath.Join(publishedRelPath, fileName)
|
||||
prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||
poolPath := storage.az.blobPath(prefixRelFilePath)
|
||||
// prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||
// FIXME: check how to integrate publishedPrefix:
|
||||
poolPath := storage.az.blobPath(fileName)
|
||||
|
||||
if storage.pathCache == nil {
|
||||
storage.pathCache = make(map[string]map[string]string)
|
||||
@@ -160,7 +157,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
}
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
err = storage.az.putFile(relFilePath, source, sourceMD5)
|
||||
err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
|
||||
if err == nil {
|
||||
pathCache[relFilePath] = sourceMD5
|
||||
} else {
|
||||
@@ -177,60 +174,57 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
||||
}
|
||||
|
||||
// Internal copy or move implementation
|
||||
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error {
|
||||
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
|
||||
const leaseDuration = 30
|
||||
leaseID := uuid.NewString()
|
||||
|
||||
serviceClient := storage.az.client.ServiceClient()
|
||||
containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||
srcBlobClient := containerClient.NewBlobClient(src)
|
||||
blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||
dstBlobURL := storage.az.blobURL(dst)
|
||||
srcBlobURL := storage.az.blobURL(src)
|
||||
leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
|
||||
if err != nil || leaseResp.StatusCode() != http.StatusCreated {
|
||||
return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
|
||||
}
|
||||
defer func() { _, _ = srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{}) }()
|
||||
srcBlobLeaseID := leaseResp.LeaseID()
|
||||
|
||||
_, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||
}
|
||||
defer func() {
|
||||
_, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
|
||||
}()
|
||||
|
||||
dstBlobClient := containerClient.NewBlobClient(dst)
|
||||
copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
|
||||
Metadata: metadata,
|
||||
})
|
||||
|
||||
copyResp, err := dstBlobURL.StartCopyFromURL(
|
||||
context.Background(),
|
||||
srcBlobURL.URL(),
|
||||
metadata,
|
||||
azblob.ModifiedAccessConditions{},
|
||||
azblob.BlobAccessConditions{},
|
||||
azblob.DefaultAccessTier,
|
||||
nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
|
||||
}
|
||||
|
||||
copyStatus := *copyResp.CopyStatus
|
||||
copyStatus := copyResp.CopyStatus()
|
||||
for {
|
||||
if copyStatus == blob.CopyStatusTypeSuccess {
|
||||
if copyStatus == azblob.CopyStatusSuccess {
|
||||
if move {
|
||||
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{
|
||||
AccessConditions: &blob.AccessConditions{
|
||||
LeaseAccessConditions: &blob.LeaseAccessConditions{
|
||||
LeaseID: &leaseID,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = srcBlobURL.Delete(
|
||||
context.Background(),
|
||||
azblob.DeleteSnapshotsOptionNone,
|
||||
azblob.BlobAccessConditions{
|
||||
LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else if copyStatus == blob.CopyStatusTypePending {
|
||||
} else if copyStatus == azblob.CopyStatusPending {
|
||||
time.Sleep(1 * time.Second)
|
||||
getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil)
|
||||
blobPropsResp, err := dstBlobURL.GetProperties(
|
||||
context.Background(),
|
||||
azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
|
||||
azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting copy progress %s", dst)
|
||||
return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
|
||||
}
|
||||
copyStatus = *getMetadata.CopyStatus
|
||||
copyStatus = blobPropsResp.CopyStatus()
|
||||
|
||||
_, err = blobLeaseClient.RenewLease(context.Background(), nil)
|
||||
_, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error renewing source blob lease %s", src)
|
||||
return fmt.Errorf("error renewing source blob lease %s", srcBlobURL)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
|
||||
@@ -245,9 +239,7 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
|
||||
|
||||
// SymLink creates a copy of src file and adds link information as meta data
|
||||
func (storage *PublishedStorage) SymLink(src string, dst string) error {
|
||||
metadata := make(map[string]*string)
|
||||
metadata["SymLink"] = &src
|
||||
return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */)
|
||||
return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
|
||||
}
|
||||
|
||||
// HardLink using symlink functionality as hard links do not exist
|
||||
@@ -257,33 +249,28 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
|
||||
|
||||
// FileExists returns true if path exists
|
||||
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
||||
serviceClient := storage.az.client.ServiceClient()
|
||||
containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||
blobClient := containerClient.NewBlobClient(path)
|
||||
_, err := blobClient.GetProperties(context.Background(), nil)
|
||||
blob := storage.az.blobURL(path)
|
||||
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
if isBlobNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("error checking if blob %s exists: %v", path, err)
|
||||
return false, err
|
||||
} else if resp.StatusCode() == http.StatusOK {
|
||||
return true, nil
|
||||
}
|
||||
return true, nil
|
||||
return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||
}
|
||||
|
||||
// ReadLink returns the symbolic link pointed to by path.
|
||||
// This simply reads text file created with SymLink
|
||||
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
||||
serviceClient := storage.az.client.ServiceClient()
|
||||
containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||
blobClient := containerClient.NewBlobClient(path)
|
||||
props, err := blobClient.GetProperties(context.Background(), nil)
|
||||
blob := storage.az.blobURL(path)
|
||||
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get blob properties: %v", err)
|
||||
return "", err
|
||||
} else if resp.StatusCode() != http.StatusOK {
|
||||
return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||
}
|
||||
|
||||
metadata := props.Metadata
|
||||
if originalBlob, exists := metadata["original_blob"]; exists {
|
||||
return *originalBlob, nil
|
||||
}
|
||||
return "", fmt.Errorf("error reading link %s: %v", path, err)
|
||||
return resp.NewMetadata()["SymLink"], nil
|
||||
}
|
||||
|
||||
+23
-26
@@ -7,11 +7,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"bytes"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/files"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
. "gopkg.in/check.v1"
|
||||
@@ -69,10 +66,8 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
||||
|
||||
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||
c.Assert(err, IsNil)
|
||||
publicAccessType := azblob.PublicAccessTypeContainer
|
||||
_, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
|
||||
Access: &publicAccessType,
|
||||
})
|
||||
cnt := s.storage.az.container
|
||||
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||
@@ -80,39 +75,41 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
||||
_, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
|
||||
cnt := s.storage.az.container
|
||||
_, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
||||
resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
|
||||
blob := s.storage.az.container.NewBlobURL(path)
|
||||
resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||
c.Assert(err, IsNil)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
|
||||
data, err := io.ReadAll(body)
|
||||
c.Assert(err, IsNil)
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
||||
serviceClient := s.storage.az.client.ServiceClient()
|
||||
containerClient := serviceClient.NewContainerClient(s.storage.az.container)
|
||||
blobClient := containerClient.NewBlobClient(path)
|
||||
_, err := blobClient.GetProperties(context.Background(), nil)
|
||||
_, err := s.storage.az.container.NewBlobURL(path).GetProperties(
|
||||
context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
c.Assert(err, NotNil)
|
||||
|
||||
storageError, ok := err.(*azcore.ResponseError)
|
||||
storageError, ok := err.(azblob.StorageError)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(storageError.StatusCode, Equals, 404)
|
||||
c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
||||
hash := md5.Sum(data)
|
||||
uploadOptions := &azblob.UploadStreamOptions{
|
||||
HTTPHeaders: &blob.HTTPHeaders{
|
||||
BlobContentMD5: hash[:],
|
||||
},
|
||||
}
|
||||
reader := bytes.NewReader(data)
|
||||
_, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
|
||||
_, err := azblob.UploadBufferToBlockBlob(
|
||||
context.Background(),
|
||||
data,
|
||||
s.storage.az.container.NewBlockBlobURL(path),
|
||||
azblob.UploadToBlockBlobOptions{
|
||||
BlobHTTPHeaders: azblob.BlobHTTPHeaders{
|
||||
ContentMD5: hash[:],
|
||||
},
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
@@ -333,7 +330,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
|
||||
|
||||
// 2nd link from pool, providing wrong path for source file
|
||||
//
|
||||
// this test should check that file already exists in Azure and skip upload (which would fail if not skipped)
|
||||
// this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
|
||||
s.prefixedStorage.pathCache = nil
|
||||
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
+33
-1
@@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/pgp"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
@@ -12,7 +14,20 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
|
||||
}
|
||||
|
||||
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.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
|
||||
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 {
|
||||
return &commander.Command{
|
||||
UsageLine: "publish",
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ Example:
|
||||
}
|
||||
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("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.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||
|
||||
@@ -230,7 +230,7 @@ Example:
|
||||
}
|
||||
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("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.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||
|
||||
@@ -151,7 +151,7 @@ This command would switch published repository (with one component) named ppa/wh
|
||||
`,
|
||||
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.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||
|
||||
@@ -115,7 +115,7 @@ Example:
|
||||
`,
|
||||
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.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
|
||||
|
||||
@@ -11,7 +11,7 @@ func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
type ProgressSuite struct {}
|
||||
type ProgressSuite struct{}
|
||||
|
||||
var _ = Suite(&ProgressSuite{})
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
||||
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
|
||||
|
||||
for _, configLocation := range configLocations {
|
||||
// FIXME: check if exists, check if readable
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
if os.IsPermission(err) || os.IsNotExist(err) {
|
||||
continue
|
||||
|
||||
@@ -14,7 +14,7 @@ func Test(t *testing.T) {
|
||||
}
|
||||
|
||||
type EtcDDBSuite struct {
|
||||
db database.Storage
|
||||
db database.Storage
|
||||
}
|
||||
|
||||
var _ = Suite(&EtcDDBSuite{})
|
||||
@@ -133,7 +133,7 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
|
||||
v, err := s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(v, DeepEquals, value)
|
||||
err = transaction.Delete(key)
|
||||
err = transaction.Delete(key)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, err = transaction.Get(key2)
|
||||
@@ -156,4 +156,3 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
|
||||
_, err = transaction.Get(key)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
|
||||
@@ -598,6 +598,7 @@ func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
|
||||
//
|
||||
// when follow-all-variants is enabled, we need to try to expand anyway,
|
||||
// as even if dependency is satisfied now, there might be other ways to satisfy dependency
|
||||
// FIXME: do not search twice
|
||||
if result.Search(dep, false, true) != nil {
|
||||
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
|
||||
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
|
||||
|
||||
+2
-1
@@ -168,6 +168,8 @@ func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
||||
|
||||
// LoadComplete loads additional information for local repo
|
||||
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
||||
repo.packageRefs = &PackageRefList{}
|
||||
|
||||
encoded, err := collection.db.Get(repo.RefKey())
|
||||
if err == database.ErrNotFound {
|
||||
return nil
|
||||
@@ -176,7 +178,6 @@ func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.packageRefs = &PackageRefList{}
|
||||
return repo.packageRefs.Decode(encoded)
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,18 @@ func (s *LocalRepoCollectionSuite) TestByUUID(c *C) {
|
||||
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) {
|
||||
repo := NewLocalRepo("local1", "Comment 1")
|
||||
c.Assert(s.collection.Update(repo), IsNil)
|
||||
|
||||
+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
|
||||
if codename == "" {
|
||||
codename, err = getCodename()
|
||||
@@ -39,7 +44,7 @@ func ParsePPA(ppaURL string, config *utils.ConfigStructure) (url string, distrib
|
||||
|
||||
distribution = codename
|
||||
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
|
||||
}
|
||||
|
||||
+65
-1
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -603,6 +604,15 @@ func (p *PublishedRepo) Key() []byte {
|
||||
return []byte("U" + p.StoragePrefix() + ">>" + p.Distribution)
|
||||
}
|
||||
|
||||
// PrefixPoolLockKey returns the task-queue resource key that serialises all
|
||||
// publish operations sharing the same pool directory under storagePrefix.
|
||||
// It must be held whenever a non-MultiDist publish may read or clean the
|
||||
// shared pool, to prevent concurrent cleanup runs from deleting each other's
|
||||
// files. See docs/Resource-Locking.md for the full key-namespace table.
|
||||
func PrefixPoolLockKey(storagePrefix string) string {
|
||||
return "P" + storagePrefix
|
||||
}
|
||||
|
||||
// RefKey is a unique id for package reference list
|
||||
func (p *PublishedRepo) RefKey(component string) []byte {
|
||||
return []byte("E" + p.UUID + component)
|
||||
@@ -1126,7 +1136,15 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
release["Label"] = p.GetLabel()
|
||||
release["Suite"] = p.GetSuite()
|
||||
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}), " ")
|
||||
if p.AcquireByHash {
|
||||
release["Acquire-By-Hash"] = "yes"
|
||||
@@ -1522,6 +1540,52 @@ func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix
|
||||
return referencedFiles, nil
|
||||
}
|
||||
|
||||
// CleanupAfterMultiDistToggle cleans up stale pool files left behind when the
|
||||
// MultiDist flag is toggled on a published repository.
|
||||
//
|
||||
// - false→true: Publish() wrote packages into pool/<distribution>/<component>/
|
||||
// but the old flat pool/<component>/ files were not removed because
|
||||
// CleanupPrefixComponentFiles only scans the new MultiDist tree.
|
||||
// A second pass with MultiDist=false cleans the legacy flat layout by
|
||||
// reusing the existing orphan-detection logic (the repo is now MultiDist=true
|
||||
// so it is excluded from the referenced-files scan, making its old pool
|
||||
// entries appear orphaned).
|
||||
//
|
||||
// - true→false: Publish() wrote packages into pool/<component>/ but the old
|
||||
// per-distribution pool/<distribution>/<component>/ directories were not
|
||||
// removed. The orphan-detection approach cannot be used here because the
|
||||
// repo's RefList still contains all packages (they just moved locations).
|
||||
// Instead we directly remove each pool/<distribution>/<component>/ directory.
|
||||
// This is safe because per-distribution pool dirs are exclusive to a single
|
||||
// prefix+distribution combination — no other published repo can share them.
|
||||
func (collection *PublishedRepoCollection) CleanupAfterMultiDistToggle(publishedStorageProvider aptly.PublishedStorageProvider,
|
||||
published *PublishedRepo, prevMultiDist bool, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
||||
if prevMultiDist == published.MultiDist {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !prevMultiDist && published.MultiDist {
|
||||
// false→true: use orphan-detection via the existing cleanup, but with
|
||||
// MultiDist temporarily set to false so it scans the flat pool layout.
|
||||
legacy := *published
|
||||
legacy.MultiDist = false
|
||||
return collection.CleanupPrefixComponentFiles(publishedStorageProvider, &legacy, cleanComponents, collectionFactory, progress)
|
||||
}
|
||||
|
||||
// true→false: directly remove the per-distribution pool directories.
|
||||
publishedStorage := publishedStorageProvider.GetPublishedStorage(published.Storage)
|
||||
for _, component := range cleanComponents {
|
||||
poolDir := filepath.Join(published.Prefix, "pool", published.Distribution, component)
|
||||
if err := publishedStorage.RemoveDirs(poolDir, progress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Remove the distribution-level pool dir if it is now empty.
|
||||
distPoolDir := filepath.Join(published.Prefix, "pool", published.Distribution)
|
||||
_ = publishedStorage.RemoveDirs(distPoolDir, progress)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
|
||||
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(publishedStorageProvider aptly.PublishedStorageProvider,
|
||||
published *PublishedRepo, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
||||
|
||||
+48
-2
@@ -433,6 +433,47 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
||||
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) {
|
||||
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -756,7 +797,10 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
|
||||
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
|
||||
_ = s.snapshotCollection.Add(snap3)
|
||||
|
||||
// Ensure that adding a second publish point with matching files doesn't give duplicate results.
|
||||
// When a second publish point references the same package (snap3 is a clone of snap2,
|
||||
// both containing p3/lonely-strangers), listReferencedFilesByComponent deduplicates by
|
||||
// package ref so the file appears only once. StrSlicesSubstract handles a single entry
|
||||
// correctly, so no duplicate is needed for cleanup safety.
|
||||
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(s.collection.Add(repo3), IsNil)
|
||||
@@ -771,7 +815,9 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
|
||||
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
||||
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
|
||||
},
|
||||
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
|
||||
"main": {
|
||||
"a/alien-arena/lonely-strangers_7.40-2_i386.deb",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,9 @@ func (l *PackageRefList) Decode(input []byte) error {
|
||||
|
||||
// ForEach calls handler for each package ref in list
|
||||
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
for _, p := range l.Refs {
|
||||
err = handler(p)
|
||||
|
||||
+12
-1
@@ -65,7 +65,7 @@ func (s *PackageRefListSuite) TestNewPackageListFromRefList(c *C) {
|
||||
list, err := NewPackageListFromRefList(reflist, coll, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(list.Len(), Equals, 4)
|
||||
c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
|
||||
c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
|
||||
|
||||
list, err = NewPackageListFromRefList(nil, coll, nil)
|
||||
c.Assert(err, IsNil)
|
||||
@@ -130,6 +130,17 @@ func (s *PackageRefListSuite) TestPackageRefListForeach(c *C) {
|
||||
c.Check(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestForEachNilList(c *C) {
|
||||
var l *PackageRefList
|
||||
called := false
|
||||
err := l.ForEach(func([]byte) error {
|
||||
called = true
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(called, Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestHas(c *C) {
|
||||
_ = s.list.Add(s.p1)
|
||||
_ = s.list.Add(s.p3)
|
||||
|
||||
+1
-1
@@ -574,7 +574,7 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
|
||||
if progress != nil {
|
||||
progress.ColoredPrintf("@y[!]@| @!skipping package %s: duplicate in packages index@|", p)
|
||||
}
|
||||
} else if err != nil {
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,12 +125,6 @@ func (s *Snapshot) Key() []byte {
|
||||
return []byte("S" + s.UUID)
|
||||
}
|
||||
|
||||
// ResourceKey is a unique identifier of the resource
|
||||
// this snapshot uses. Instead of uuid it uses name
|
||||
// which needs to be unique as well.
|
||||
func (s *Snapshot) ResourceKey() []byte {
|
||||
return []byte("S" + s.Name)
|
||||
}
|
||||
|
||||
// RefKey is a unique id for package reference list
|
||||
func (s *Snapshot) RefKey() []byte {
|
||||
|
||||
@@ -31,8 +31,7 @@ func BenchmarkSnapshotCollectionForEach(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
collection = NewSnapshotCollection(db)
|
||||
|
||||
|
||||
_ = collection.ForEach(func(s *Snapshot) error {
|
||||
_ = collection.ForEach(func(s *Snapshot) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
+7
-7
@@ -30,16 +30,16 @@ func CompareVersions(ver1, ver2 string) int {
|
||||
|
||||
// parseVersions breaks down full version to components (possibly empty)
|
||||
func parseVersion(ver string) (epoch, upstream, debian string) {
|
||||
i := strings.LastIndex(ver, "-")
|
||||
if i != -1 {
|
||||
debian, ver = ver[i+1:], ver[:i]
|
||||
}
|
||||
|
||||
i = strings.Index(ver, ":")
|
||||
i := strings.Index(ver, ":")
|
||||
if i != -1 {
|
||||
epoch, ver = ver[:i], ver[i+1:]
|
||||
}
|
||||
|
||||
i = strings.Index(ver, "-")
|
||||
if i != -1 {
|
||||
debian, ver = ver[i+1:], ver[:i]
|
||||
}
|
||||
|
||||
upstream = ver
|
||||
|
||||
return
|
||||
@@ -50,7 +50,7 @@ func compareLexicographic(s1, s2 string) int {
|
||||
i := 0
|
||||
l1, l2 := len(s1), len(s2)
|
||||
|
||||
for !(i == l1 && i == l2) { // break if s1 equal to s2
|
||||
for !(i == l1 && i == l2) { // break if s1 equal to s2
|
||||
|
||||
if i == l2 {
|
||||
// s1 is longer than s2
|
||||
|
||||
+3
-2
@@ -20,10 +20,10 @@ func (s *VersionSuite) TestParseVersion(c *C) {
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", "1"})
|
||||
|
||||
e, u, d = parseVersion("1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3-pre4", "1"})
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3", "pre4-1"})
|
||||
|
||||
e, u, d = parseVersion("4:1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3-pre4", "1"})
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3", "pre4-1"})
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestCompareLexicographic(c *C) {
|
||||
@@ -100,6 +100,7 @@ func (s *VersionSuite) TestCompareVersions(c *C) {
|
||||
c.Check(CompareVersions("1.0-133-avc", "1.0"), Equals, 1)
|
||||
|
||||
c.Check(CompareVersions("5.2.0.3", "5.2.0.283"), Equals, -1)
|
||||
c.Check(CompareVersions("4.3.5a", "4.3.5-rc3-1"), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestParseDependency(c *C) {
|
||||
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
aptly (1.6.0+ds1-1) unstable; urgency=medium
|
||||
|
||||
- aptly-api: configuration file is now /etc/aptly.conf, and `rootDir`
|
||||
defaults to `~/.aptly`
|
||||
|
||||
- aptly-api: default port is 8080, as declared in
|
||||
`/etc/default/aptly-api`
|
||||
|
||||
- aptly: swagger support is disabled, but will be re-enabled after the
|
||||
corresponding golang packages make it to unstable
|
||||
|
||||
- aptly: default format is yaml, but the old format is still supported
|
||||
for now
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 29 Dec 2024 08:46:07 +0100
|
||||
|
||||
aptly (1.3.0+ds1-2.2) unstable; urgency=medium
|
||||
|
||||
This version tries to fix the database backwards compatibility,
|
||||
so you don't need to rebuild the database if you upgrade from
|
||||
aptly <= 1.3.0-6.
|
||||
|
||||
However some fields are missing, like created time of a snapshot.
|
||||
|
||||
-- Shengjing Zhu <zhsj@debian.org> Sat, 13 Apr 2019 23:26:39 +0800
|
||||
|
||||
aptly (1.3.0+ds1-2) unstable; urgency=medium
|
||||
|
||||
* The database created by aptly <= 1.3.0-6 is not compatible
|
||||
with greater versions. Users must create a new database.
|
||||
The `aptly db recover` will not fix the issue.
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Fri, 26 Oct 2018 13:20:53 -0400
|
||||
Vendored
+5
-1
@@ -70,6 +70,9 @@ ppa_distributor_id: ubuntu
|
||||
# Codename for short PPA url expansion
|
||||
ppa_codename: ""
|
||||
|
||||
# PPA Base URL (default: launchpad)
|
||||
# # ppa_baseurl: http://ppa.launchpad.net
|
||||
|
||||
|
||||
# Aptly Server
|
||||
###############
|
||||
@@ -80,8 +83,9 @@ serve_in_api_mode: false
|
||||
# Enable metrics for Prometheus client
|
||||
enable_metrics_endpoint: false
|
||||
|
||||
# Not implemented in this version.
|
||||
# Enable API documentation on /docs
|
||||
enable_swagger_endpoint: false
|
||||
#enable_swagger_endpoint: false
|
||||
|
||||
# OBSOLETE: use via url param ?_async=true
|
||||
async_api: false
|
||||
|
||||
Vendored
+514
-43
@@ -1,51 +1,522 @@
|
||||
aptly (1.6.2) stable; urgency=medium
|
||||
aptly (1.6.2-3) unstable; urgency=medium
|
||||
|
||||
* doc: add swagger doc for /api/gpg/key (https://github.com/aptly-dev/aptly/pull/1456)
|
||||
* bash-completion: include global options in aptly command completions (https://github.com/aptly-dev/aptly/pull/1452)
|
||||
* Bump golang.org/x/net from 0.33.0 to 0.38.0 (https://github.com/aptly-dev/aptly/pull/1443)
|
||||
* Bump golang.org/x/crypto from 0.31.0 to 0.35.0 (https://github.com/aptly-dev/aptly/pull/1441)
|
||||
* Remove corrupt package references in `db recover` (https://github.com/aptly-dev/aptly/pull/1445)
|
||||
* Fix upload of unchanged packages in S3 (https://github.com/aptly-dev/aptly/pull/1440)
|
||||
* use go 1.24 (https://github.com/aptly-dev/aptly/pull/1439)
|
||||
[ Sébastien Delafond ]
|
||||
* tests: disable t04_mirror/create/CreateMirror18Test (Closes: #1135740)
|
||||
* tests: disable t12_api/gpg/GPGAPITestAddKey (Closes: #1135672)
|
||||
* d/control: bump-up Standards-Version
|
||||
|
||||
-- André Roth <neolynx@gmail.com> Mon, 09 Jun 2025 13:45:15 +0200
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 05 May 2026 18:14:44 +0200
|
||||
|
||||
aptly (1.6.1) stable; urgency=medium
|
||||
aptly (1.6.2-2) unstable; urgency=medium
|
||||
|
||||
* update golang-github-syndtr-goleveldb-dev dependency (v1.0.1-0.20220721030215-126854af5e6d) to fix segfault on arm64
|
||||
(bug in golang-github-golang-snappy-dev)
|
||||
* allow snapshotting empty mirrors again (regression)
|
||||
* debian compliance: add postrm (note: `apt purge aptly-api` will remove all data in ~aptly-api/)
|
||||
* update other dependencies (x/net 0.33.0, gin-gonic/gin 1.9.1)
|
||||
[ Sébastien Delafond ]
|
||||
* Remove Built-Using
|
||||
* Mark patches as "Forwarded: not-needed"
|
||||
|
||||
-- André Roth <neolynx@gmail.com> Sat, 15 Feb 2025 13:03:16 +0100
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 21 Nov 2025 15:46:51 +0100
|
||||
|
||||
aptly (1.6.0) stable; urgency=medium
|
||||
aptly (1.6.2-1) unstable; urgency=medium
|
||||
|
||||
* support reading filters from file or stdin
|
||||
* fix mirroring source packages
|
||||
* support yaml config per default
|
||||
* provide swagger API documentation
|
||||
* provide API for querying aptly storage usage
|
||||
* provide snapshot pull via API
|
||||
* support creating repos from snapshots
|
||||
* fix mirroring flat remote repos
|
||||
* support skeleton files for publishing
|
||||
* use new azure sdk
|
||||
* support updating the components of a published repo
|
||||
* support publishing multiple distributions (-multi-dist)
|
||||
* support etcd database
|
||||
* allow slash (/) in distribution names
|
||||
* support for storing the "local" pool on Azure
|
||||
* provide copy package API
|
||||
* fix publish concurrency and improve performance
|
||||
* improved mirroring
|
||||
* fix download throttling
|
||||
* fix resuming package downloads
|
||||
* fix ignoring signatures
|
||||
* fix packages dependency resolution (Virtual Packages, version numbers in Provides)
|
||||
* improved S3 support and performance
|
||||
* fix race condition with goleveldb
|
||||
* use go 1.22
|
||||
[ Sébastien Delafond ]
|
||||
* d/watch: v5
|
||||
* Bump up Standards-Version
|
||||
* Remove +ds suffix
|
||||
* Add Static-Built-Using
|
||||
* New upstream version 1.6.2
|
||||
|
||||
-- André Roth <neolynx@gmail.com> Tue, 24 Dec 2024 17:44:35 +0100
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 24 Sep 2025 06:19:54 +0200
|
||||
|
||||
aptly (1.6.1+ds1-3) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* tests: declare needs-internet for system-test
|
||||
* tests: disable unit test TestVerifyClearsigned & system test
|
||||
CreateMirror31Test (Closes: #1108828)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 08 Jul 2025 14:12:52 +0200
|
||||
|
||||
aptly (1.6.1+ds1-2) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Do not re-publish unchanged files to S3 every single time (Closes: #1104299)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Mon, 28 Apr 2025 15:38:43 +0200
|
||||
|
||||
aptly (1.6.1+ds1-1) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* New upstream version 1.6.1
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Mon, 24 Feb 2025 09:04:35 +0100
|
||||
|
||||
aptly (1.6.0+ds1-8) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* tests: disable riscv64-flaky EditRepo4Test
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 21 Feb 2025 06:54:18 +0100
|
||||
|
||||
aptly (1.6.0+ds1-7) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* tests: clean way to disable single tests, disable s390-flaky CreateMirror35Test
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 20 Feb 2025 07:26:41 +0100
|
||||
|
||||
aptly (1.6.0+ds1-6) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* tests: disable system-test's etcd tests as the corresponding fixture is also arch-specific
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 19 Feb 2025 14:02:19 +0100
|
||||
|
||||
aptly (1.6.0+ds1-5) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* tests: do not use upstream's etcd installer
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 19 Feb 2025 07:20:44 +0100
|
||||
|
||||
aptly (1.6.0+ds1-4) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* postrm: remove aptly-api user and home directory on purge
|
||||
* tests: use extensive coverage from make's test and system-test
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 18 Feb 2025 10:36:13 +0100
|
||||
|
||||
aptly (1.6.0+ds1-3) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* aptly.conf: fix s3 example
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Mon, 13 Jan 2025 14:51:29 +0100
|
||||
|
||||
aptly (1.6.0+ds1-2) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Document disabled swagger in default config file
|
||||
* Rediff patches: swagger references in man page
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Mon, 30 Dec 2024 11:11:07 +0100
|
||||
|
||||
aptly (1.6.0+ds1-1) unstable; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Official 1.6 release
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 27 Dec 2024 14:23:29 +0100
|
||||
|
||||
aptly (1.6.0+ds1~beta3-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Latest upstream version
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 11 Dec 2024 18:16:19 +0100
|
||||
|
||||
aptly (1.6.0+ds1~beta2-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Latest upstream version
|
||||
|
||||
[ André Roth ]
|
||||
* set systemd service file limit to 32768
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 21 Nov 2024 16:08:05 +0100
|
||||
|
||||
aptly (1.6.0+ds1~beta1-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Latest upstream version
|
||||
* Disable "use new azure-sdk"
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 18:52:06 +0100
|
||||
|
||||
aptly (1.6.0+ds1~alpha5-1) experimental; urgency=medium
|
||||
|
||||
* Sort out dependencies on gpg*
|
||||
* Reduce diff with upstream some more
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 15:59:23 +0100
|
||||
|
||||
aptly (1.6.0+ds1~alpha4-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Remove build-dep on git
|
||||
* Adjust systemd unit for aptly-api
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 14:51:49 +0100
|
||||
|
||||
aptly (1.6.0+ds1~alpha3-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Latest upstream version
|
||||
* Remove useless maintainer scripts
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 07:50:06 +0100
|
||||
|
||||
aptly (1.6.0+ds1~alpha2-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Latest upstream version
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sat, 16 Nov 2024 14:42:10 +0100
|
||||
|
||||
aptly (1.6.0+ds1~alpha1-2) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Add zsh completion
|
||||
* aptly-api: revert arch:all, so mv_conffile does the right thing
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 16 Oct 2024 09:10:05 +0200
|
||||
|
||||
aptly (1.6.0+ds1~alpha1-1) experimental; urgency=medium
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* d/patches: remove old patch, and disable swagger support
|
||||
* d/control: bump up Standards-Version
|
||||
* Adjust packaging for 1.6.0~alpha1
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 15 Oct 2024 14:40:57 +0200
|
||||
|
||||
aptly (1.5.0+ds1-2) unstable; urgency=medium
|
||||
|
||||
[ Shengjing Zhu ]
|
||||
* Replace golang-gopkg-cheggaaa-pb.v1-dev with
|
||||
golang-github-cheggaaa-pb-dev (Closes: #1050900)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Mon, 04 Sep 2023 08:49:36 +0200
|
||||
|
||||
aptly (1.5.0+ds1-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* New upstream release (Closes: #1022721), including fix for "Order of
|
||||
fields in Packages/Sources is unpredictable" (Closes: #907121).
|
||||
|
||||
-- Roland Mas <lolando@debian.org> Tue, 31 Jan 2023 14:47:04 +0100
|
||||
|
||||
aptly (1.4.0+ds1-7) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Add support for zstd compression (Closes: #1010465)
|
||||
|
||||
-- Anton Gladky <gladk@debian.org> Tue, 17 May 2022 22:42:29 +0200
|
||||
|
||||
aptly (1.4.0+ds1-6) unstable; urgency=medium
|
||||
|
||||
* Conflict on gpgv1 (Closes: #990821)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 04 Nov 2021 10:24:53 +0100
|
||||
|
||||
aptly (1.4.0+ds1-5) unstable; urgency=medium
|
||||
|
||||
* Conflict on gnupg1 (Closes: #990821)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 14 Oct 2021 18:43:04 +0200
|
||||
|
||||
aptly (1.4.0+ds1-4) unstable; urgency=medium
|
||||
|
||||
* Install correct bash completion snippet (Closes: #984979)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 11 Mar 2021 15:20:57 +0100
|
||||
|
||||
aptly (1.4.0+ds1-3) unstable; urgency=medium
|
||||
|
||||
* Fix s3 etag issue (Closes: #983877)
|
||||
* Bump-up d/watch version
|
||||
* Bump-up Standards-Version
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 03 Mar 2021 10:50:51 +0100
|
||||
|
||||
aptly (1.4.0+ds1-2) unstable; urgency=medium
|
||||
|
||||
* Use pipeline from salsa-ci-team
|
||||
* Allow reprotest failure
|
||||
* Pass version from d/rules (Closes: #968585)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 21 Aug 2020 10:13:44 +0200
|
||||
|
||||
aptly (1.4.0+ds1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 1.4.0+ds1
|
||||
* Rediff patches
|
||||
* Depend on gnupg 2
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 22 Dec 2019 15:16:25 +0100
|
||||
|
||||
aptly (1.3.0+ds1-4) unstable; urgency=medium
|
||||
|
||||
[ Debian Janitor ]
|
||||
* Rename obsolete path debian/tests/control.autodep8 to debian/tests/control.
|
||||
* Use secure URI in Homepage field.
|
||||
* Bump debhelper from old 11 to 12.
|
||||
* Set debhelper-compat version in Build-Depends.
|
||||
|
||||
[ Sébastien Delafond ]
|
||||
* Bump up Standards-Version
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 22 Dec 2019 14:10:19 +0100
|
||||
|
||||
aptly (1.3.0+ds1-3) unstable; urgency=medium
|
||||
|
||||
* Build-Depend on golang-golang-x-tools-dev instead of golang-go.tools (Closes: #945884)
|
||||
* Lintian fix
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sat, 21 Dec 2019 10:29:09 +0100
|
||||
|
||||
aptly (1.3.0+ds1-2.3) unstable; urgency=medium
|
||||
|
||||
* Non-maintainer upload.
|
||||
* Remove myself from uploaders.
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Sun, 15 Sep 2019 19:27:47 -0400
|
||||
|
||||
aptly (1.3.0+ds1-2.2) unstable; urgency=medium
|
||||
|
||||
* Non-maintainer upload.
|
||||
* Add patch to fix DB backwards compatibility (Closes: #911924)
|
||||
* Fix struct field tag typo
|
||||
* Update debian/NEWS about DB compatibility
|
||||
|
||||
-- Shengjing Zhu <zhsj@debian.org> Tue, 16 Apr 2019 00:18:23 +0800
|
||||
|
||||
aptly (1.3.0+ds1-2.1) unstable; urgency=medium
|
||||
|
||||
[ Shengjing Zhu ]
|
||||
* Non-maintainer upload.
|
||||
* Add patch to fix UUID struct field not encoded in msgpack (Closes: #923866)
|
||||
|
||||
[ Tobias Frost ]
|
||||
* Prepare upload.
|
||||
|
||||
-- Tobias Frost <tobi@debian.org> Fri, 05 Apr 2019 17:19:14 +0200
|
||||
|
||||
aptly (1.3.0+ds1-2) unstable; urgency=medium
|
||||
|
||||
* Add NEWS to warn about database compatibility.
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Fri, 26 Oct 2018 13:22:38 -0400
|
||||
|
||||
aptly (1.3.0+ds1-1) unstable; urgency=medium
|
||||
|
||||
[ Ondřej Nový ]
|
||||
* d/changelog: Remove trailing whitespaces
|
||||
|
||||
[ Alexandre Viau ]
|
||||
* d/watch: Append +ds suffix.
|
||||
* d/copyright: ignore vendor/*. (Closes: #902128)
|
||||
* d/copyright: remove vendor/* sections.
|
||||
* d/copyright: MIT -> Expat.
|
||||
* d/control: add vendor/* build dependencies.
|
||||
* Patch: Use Debian's uuid package.
|
||||
* Patch: Use Debian's lzma package.
|
||||
* d/rules: remove trailing whitespace.
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Mon, 15 Oct 2018 11:54:03 -0400
|
||||
|
||||
aptly (1.3.0-6) unstable; urgency=medium
|
||||
|
||||
* Combine autodep8 and autopkgtest.
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Fri, 29 Jun 2018 18:11:41 -0400
|
||||
|
||||
aptly (1.3.0-5) unstable; urgency=medium
|
||||
|
||||
* Fix syntax-error-in-dep5-copyright.
|
||||
* Fix unnecessary-testsuite-autopkgtest-field.
|
||||
* Fix broken autopkgtest.
|
||||
* Depend on gpgv1.
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Tue, 26 Jun 2018 23:01:36 -0400
|
||||
|
||||
aptly (1.3.0-4) unstable; urgency=medium
|
||||
|
||||
* Document #902128 in debian/copyright
|
||||
* Point debian/watch to new git repo on GitHub
|
||||
* Add simple autopkgtest
|
||||
* Add debian/.gitlab-ci.yml
|
||||
* Depend on gnupg1 (Closes: #902419)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 26 Jun 2018 14:24:34 +0200
|
||||
|
||||
aptly (1.3.0-3) unstable; urgency=medium
|
||||
|
||||
* Create aptly-api package. (Closes: #902032)
|
||||
|
||||
-- Alexandre Viau <aviau@debian.org> Fri, 22 Jun 2018 13:51:50 -0400
|
||||
|
||||
aptly (1.3.0-2) unstable; urgency=medium
|
||||
|
||||
* Team upload, many thanks to Alexandre Viau for this work
|
||||
* Switch to dh-golang. (Closes: #902038)
|
||||
* Fix vcs-field-not-canonical.
|
||||
* Fix insecure-copyright-format-uri.
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 22 Jun 2018 13:53:11 +0200
|
||||
|
||||
aptly (1.3.0-1) unstable; urgency=medium
|
||||
|
||||
* Fix copyright
|
||||
* New upstream version 1.3.0
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 21 Jun 2018 15:14:57 +0200
|
||||
|
||||
aptly (1.2.0-4) unstable; urgency=medium
|
||||
|
||||
* Enable Built-Using in debian/control (thanks M. Staperberg)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 06 Mar 2018 10:10:42 +0100
|
||||
|
||||
aptly (1.2.0-3) unstable; urgency=medium
|
||||
|
||||
* Switch to DEP 14
|
||||
* Update Vcs-* to point to salsa.d.o
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 16 Feb 2018 17:02:25 +0100
|
||||
|
||||
aptly (1.2.0-2) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Build-Depend on golang-any, not golang
|
||||
* Set XS-Go-Import-Path
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Sat, 10 Feb 2018 18:41:37 +0100
|
||||
|
||||
aptly (1.2.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 1.2.0
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 03 Jan 2018 13:43:30 +0100
|
||||
|
||||
aptly (1.1.1-2) unstable; urgency=medium
|
||||
|
||||
* Support both uncompressed control.tar, and xz-compressed
|
||||
control.tar.xz. Thanks to Boyuan Yang for the patch (Closes: 879718)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 02 Nov 2017 13:23:29 +0100
|
||||
|
||||
aptly (1.1.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 1.1.1
|
||||
* Use bash-completion snippet from upstream
|
||||
* Bump up Standards-Version
|
||||
* Update debian/copyright
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 02 Nov 2017 09:03:23 +0100
|
||||
|
||||
aptly (1.0.1-1) unstable; urgency=medium
|
||||
|
||||
* Imported Upstream version 1.0.1
|
||||
* Update debian/copyright
|
||||
* Adapt debian/rules
|
||||
* Update Vcs-Git
|
||||
* Bump-up Standards-Version
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 18 Jul 2017 11:53:29 +0200
|
||||
|
||||
aptly (0.9.7-1) unstable; urgency=medium
|
||||
|
||||
* Imported new upstream version 0.9.7
|
||||
* Add new licenses and copyrights info
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 24 May 2016 09:17:08 +0200
|
||||
|
||||
aptly (0.9.6-1) unstable; urgency=medium
|
||||
|
||||
* Import new upstream version 0.9.6
|
||||
* Add dependency on xz-utils
|
||||
* Licenses and copyrights
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 10 Feb 2016 18:28:51 +0100
|
||||
|
||||
aptly (0.9.5-2) unstable; urgency=medium
|
||||
|
||||
* Remove empty component in GOPATH (Closes: #793838)
|
||||
|
||||
[ Sebastien Delafond ]
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sun, 16 Aug 2015 18:42:22 +0200
|
||||
|
||||
aptly (0.9.5-1) unstable; urgency=medium
|
||||
|
||||
* Imported Upstream version 0.9.5
|
||||
* Up-to-date bash completion
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 15 May 2015 10:46:51 +0200
|
||||
|
||||
aptly (0.9.1-1) unstable; urgency=medium
|
||||
|
||||
* Imported Upstream version 0.9.1
|
||||
* Document licenses and copyrights for new dependencies
|
||||
* Suggest graphviz
|
||||
* Proper name for bash-completion file
|
||||
* Bump-up Standards-Version
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sat, 02 May 2015 12:57:16 +0200
|
||||
|
||||
aptly (0.8-3) unstable; urgency=medium
|
||||
|
||||
* Correct Vcs-Browser entry (Closes: #764622)
|
||||
* Correct dependency from "gpg" to "gnupg" (Closes: #764619)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 09 Oct 2014 18:41:38 +0200
|
||||
|
||||
aptly (0.8-2) unstable; urgency=medium
|
||||
|
||||
* Add missing dependencies on bzip2, gpg and gpgv
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 09 Oct 2014 11:15:21 +0200
|
||||
|
||||
aptly (0.8-1) unstable; urgency=medium
|
||||
|
||||
* Imported Upstream version 0.8
|
||||
* Document new copyrights and licenses
|
||||
* Add bash completion snippet
|
||||
* Reference full paths in debian/copyright, and remove unused
|
||||
references, so lintian does not complain
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Sat, 04 Oct 2014 17:46:24 +0200
|
||||
|
||||
aptly (0.7.1-1) unstable; urgency=medium
|
||||
|
||||
* Imported Upstream version 0.7.1
|
||||
* Add copyright information for new libraries
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 10 Sep 2014 10:03:27 +0200
|
||||
|
||||
aptly (0.5-5) unstable; urgency=medium
|
||||
|
||||
* Turn off verbose mode
|
||||
* Correct Vcs-* information
|
||||
* gocov is licensed MIT, not BSD-3
|
||||
* Add missing MIT license text
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 09 Sep 2014 09:20:40 +0200
|
||||
|
||||
aptly (0.5-4) unstable; urgency=medium
|
||||
|
||||
* Collect licenses by going over files one by one
|
||||
* Use packaged golang-go.tools
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Thu, 10 Jul 2014 00:49:09 +0200
|
||||
|
||||
aptly (0.5-3) unstable; urgency=low
|
||||
|
||||
* Licensing:
|
||||
- goleveldb is BSD-2, not BSD-3
|
||||
- _vendor/src/code.google.com/p/gographviz/scanner/scanner is BSD-3,
|
||||
copyright 2009 The Go Authors
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Wed, 28 May 2014 10:04:01 +0200
|
||||
|
||||
aptly (0.5-2) unstable; urgency=low
|
||||
|
||||
* Go interpreter not needed at runtime
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Fri, 02 May 2014 12:04:02 +0200
|
||||
|
||||
aptly (0.5-1) unstable; urgency=low
|
||||
|
||||
* Initial release (Closes: #746343)
|
||||
|
||||
-- Sebastien Delafond <seb@debian.org> Tue, 29 Apr 2014 15:47:31 +0200
|
||||
|
||||
Vendored
+7
-16
@@ -1,7 +1,7 @@
|
||||
Source: aptly
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: André Roth <neolynx@gmail.com>
|
||||
Maintainer: Sebastien Delafond <seb@debian.org>
|
||||
Build-Depends: bash-completion,
|
||||
debhelper-compat (= 13),
|
||||
dh-golang,
|
||||
@@ -77,20 +77,18 @@ Build-Depends: bash-completion,
|
||||
golang-go.uber-zap-dev,
|
||||
golang-etcd-server-dev (>= 3.5.15-7),
|
||||
golang-gopkg-yaml.v3-dev,
|
||||
git
|
||||
Standards-Version: 4.7.0
|
||||
Standards-Version: 4.7.4
|
||||
Homepage: https://www.aptly.info
|
||||
Vcs-Git: https://github.com/aptly-dev/aptly.git
|
||||
Vcs-Browser: https://github.com/aptly-dev/aptly
|
||||
Vcs-Git: https://salsa.debian.org/debian/aptly.git
|
||||
Vcs-Browser: https://salsa.debian.org/debian/aptly
|
||||
XS-Go-Import-Path: github.com/aptly-dev/aptly
|
||||
Testsuite: autopkgtest-pkg-go
|
||||
|
||||
Package: aptly
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, bzip2, xz-utils, gpgv, gpg
|
||||
Suggests: graphviz
|
||||
Static-Built-Using: ${misc:Static-Built-Using}
|
||||
Conflicts: gnupg1, gpgv1
|
||||
Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using}
|
||||
Suggests: graphviz
|
||||
Description: Swiss army knife for Debian repository management - main package
|
||||
It offers several features making it easy to manage Debian package
|
||||
repositories:
|
||||
@@ -107,7 +105,7 @@ Description: Swiss army knife for Debian repository management - main package
|
||||
This is the main package, it contains the aptly command-line utility.
|
||||
|
||||
Package: aptly-api
|
||||
Architecture: any
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, aptly
|
||||
Description: Swiss army knife for Debian repository management - API
|
||||
It offers several features making it easy to manage Debian package
|
||||
@@ -123,10 +121,3 @@ Description: Swiss army knife for Debian repository management - API
|
||||
- merge two or more snapshots into one
|
||||
.
|
||||
This package contains the aptly-api service.
|
||||
|
||||
Package: aptly-dbg
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}
|
||||
Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using}
|
||||
Description: Debian repository management tool (debug files)
|
||||
Debug symbols for aptly
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
debian-branch = debian/master
|
||||
upstream-branch = upstream/latest
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
usr/bin/files
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
From: =?utf-8?q?Andr=C3=A9_Roth?= <neolynx@gmail.com>
|
||||
Date: Tue, 15 Oct 2024 12:09:33 +0200
|
||||
Subject: Disable swagger
|
||||
|
||||
This can be enabled once the following modules make it into Debian:
|
||||
|
||||
- github.com/swaggo/files v1.0.1
|
||||
- github.com/swaggo/gin-swagger v1.6.0
|
||||
- github.com/swaggo/swag v1.16.3
|
||||
|
||||
Forwarded: not-needed
|
||||
---
|
||||
api/router.go | 22 +++++++++++-----------
|
||||
docs/index.go | 10 ----------
|
||||
docs/index.go.disabled | 10 ++++++++++
|
||||
man/aptly.1 | 3 ++-
|
||||
man/aptly.1.ronn.tmpl | 3 ++-
|
||||
5 files changed, 25 insertions(+), 23 deletions(-)
|
||||
delete mode 100644 docs/index.go
|
||||
create mode 100644 docs/index.go.disabled
|
||||
|
||||
diff --git a/api/router.go b/api/router.go
|
||||
index 3cd7d42..cf53cd2 100644
|
||||
--- a/api/router.go
|
||||
+++ b/api/router.go
|
||||
@@ -11,9 +11,9 @@
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
- "github.com/aptly-dev/aptly/docs"
|
||||
- swaggerFiles "github.com/swaggo/files"
|
||||
- ginSwagger "github.com/swaggo/gin-swagger"
|
||||
+ // _ "github.com/aptly-dev/aptly/docs" // import docs
|
||||
+ // swaggerFiles "github.com/swaggo/files"
|
||||
+ // ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
var context *ctx.AptlyContext
|
||||
@@ -63,14 +63,14 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||
|
||||
router.Use(gin.Recovery(), gin.ErrorLogger())
|
||||
|
||||
- if c.Config().EnableSwaggerEndpoint {
|
||||
- router.GET("docs.html", func(c *gin.Context) {
|
||||
- c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||
- })
|
||||
- router.Use(redirectSwagger)
|
||||
- url := ginSwagger.URL("/docs/doc.json")
|
||||
- router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||
- }
|
||||
+ // if c.Config().EnableSwaggerEndpoint {
|
||||
+ // router.GET("docs.html", func(c *gin.Context) {
|
||||
+ // c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||
+ // })
|
||||
+ // router.Use(redirectSwagger)
|
||||
+ // url := ginSwagger.URL("/docs/doc.json")
|
||||
+ // router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||
+ // }
|
||||
|
||||
if c.Config().EnableMetricsEndpoint {
|
||||
MetricsCollectorRegistrar.Register(router)
|
||||
diff --git a/docs/index.go b/docs/index.go
|
||||
deleted file mode 100644
|
||||
index ca4c914..0000000
|
||||
--- a/docs/index.go
|
||||
+++ /dev/null
|
||||
@@ -1,10 +0,0 @@
|
||||
-package docs
|
||||
-
|
||||
-import (
|
||||
- _ "embed" // embed html below
|
||||
-
|
||||
- _ "github.com/swaggo/swag" // make sure swag is in go.mod
|
||||
-)
|
||||
-
|
||||
-//go:embed docs.html
|
||||
-var DocsHTML []byte
|
||||
diff --git a/docs/index.go.disabled b/docs/index.go.disabled
|
||||
new file mode 100644
|
||||
index 0000000..ca4c914
|
||||
--- /dev/null
|
||||
+++ b/docs/index.go.disabled
|
||||
@@ -0,0 +1,10 @@
|
||||
+package docs
|
||||
+
|
||||
+import (
|
||||
+ _ "embed" // embed html below
|
||||
+
|
||||
+ _ "github.com/swaggo/swag" // make sure swag is in go.mod
|
||||
+)
|
||||
+
|
||||
+//go:embed docs.html
|
||||
+var DocsHTML []byte
|
||||
diff --git a/man/aptly.1 b/man/aptly.1
|
||||
index bd6ad22..11ed990 100644
|
||||
--- a/man/aptly.1
|
||||
+++ b/man/aptly.1
|
||||
@@ -111,8 +111,9 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Enable metrics for Prometheus client
|
||||
"enableMetricsEndpoint": false,
|
||||
|
||||
+ // Not implemented in this version\.
|
||||
// Enable API documentation on /docs
|
||||
- "enableSwaggerEndpoint": false,
|
||||
+ //"enableSwaggerEndpoint": false,
|
||||
|
||||
// OBSOLETE: use via url param ?_async=true
|
||||
"AsyncAPI": false,
|
||||
diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl
|
||||
index 203cc7f..ed2c87c 100644
|
||||
--- a/man/aptly.1.ronn.tmpl
|
||||
+++ b/man/aptly.1.ronn.tmpl
|
||||
@@ -100,8 +100,9 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Enable metrics for Prometheus client
|
||||
"enableMetricsEndpoint": false,
|
||||
|
||||
+ // Not implemented in this version.
|
||||
// Enable API documentation on /docs
|
||||
- "enableSwaggerEndpoint": false,
|
||||
+ //"enableSwaggerEndpoint": false,
|
||||
|
||||
// OBSOLETE: use via url param ?_async=true
|
||||
"AsyncAPI": false,
|
||||
+880
@@ -0,0 +1,880 @@
|
||||
From: =?utf-8?q?Andr=C3=A9_Roth?= <neolynx@gmail.com>
|
||||
Date: Sun, 17 Nov 2024 17:58:04 +0100
|
||||
Subject: Disable new azure-sdk
|
||||
|
||||
This reverts commit e2cbd637b82a153a6756f2af0519e8fe769ee9ab.
|
||||
|
||||
We can enable this once the golang-github-azure-azure-sdk-for-go-dev
|
||||
packages are upgraded in Debian:
|
||||
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||
|
||||
Forwarded: not-needed
|
||||
---
|
||||
azure/azure.go | 89 +++++++++++++++-----------------
|
||||
azure/package_pool.go | 45 ++++++++--------
|
||||
azure/package_pool_test.go | 8 ++-
|
||||
azure/public.go | 125 +++++++++++++++++++++------------------------
|
||||
azure/public_test.go | 49 +++++++++---------
|
||||
context/context.go | 1 +
|
||||
deb/list.go | 1 +
|
||||
go.mod | 8 +--
|
||||
go.sum | 52 +++++++++++--------
|
||||
9 files changed, 185 insertions(+), 193 deletions(-)
|
||||
|
||||
diff --git a/azure/azure.go b/azure/azure.go
|
||||
index 3f12678..b313f90 100644
|
||||
--- a/azure/azure.go
|
||||
+++ b/azure/azure.go
|
||||
@@ -5,35 +5,28 @@
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
- "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
- "os"
|
||||
+ "net/url"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
)
|
||||
|
||||
func isBlobNotFound(err error) bool {
|
||||
- var respErr *azcore.ResponseError
|
||||
- if errors.As(err, &respErr) {
|
||||
- return respErr.StatusCode == 404 // BlobNotFound
|
||||
- }
|
||||
- return false
|
||||
+ storageError, ok := err.(azblob.StorageError)
|
||||
+ return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
|
||||
}
|
||||
|
||||
type azContext struct {
|
||||
- client *azblob.Client
|
||||
- container string
|
||||
+ container azblob.ContainerURL
|
||||
prefix string
|
||||
}
|
||||
|
||||
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
|
||||
- cred, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
+ credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -42,14 +35,15 @@ func newAzContext(accountName, accountKey, container, prefix, endpoint string) (
|
||||
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
|
||||
}
|
||||
|
||||
- serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil)
|
||||
+ url, err := url.Parse(fmt.Sprintf("%s/%s", endpoint, container))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+ containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
|
||||
+
|
||||
result := &azContext{
|
||||
- client: serviceClient,
|
||||
- container: container,
|
||||
+ container: containerURL,
|
||||
prefix: prefix,
|
||||
}
|
||||
|
||||
@@ -60,6 +54,10 @@ func (az *azContext) blobPath(path string) string {
|
||||
return filepath.Join(az.prefix, path)
|
||||
}
|
||||
|
||||
+func (az *azContext) blobURL(path string) azblob.BlobURL {
|
||||
+ return az.container.NewBlobURL(az.blobPath(path))
|
||||
+}
|
||||
+
|
||||
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
|
||||
const delimiter = "/"
|
||||
paths = make([]string, 0, 1024)
|
||||
@@ -69,33 +67,27 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
||||
prefix += delimiter
|
||||
}
|
||||
|
||||
- ctx := context.Background()
|
||||
- maxResults := int32(1)
|
||||
- pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{
|
||||
- Prefix: &prefix,
|
||||
- MaxResults: &maxResults,
|
||||
- Include: azblob.ListBlobsInclude{Metadata: true},
|
||||
- })
|
||||
-
|
||||
- // Iterate over each page
|
||||
- for pager.More() {
|
||||
- page, err := pager.NextPage(ctx)
|
||||
+ for marker := (azblob.Marker{}); marker.NotDone(); {
|
||||
+ listBlob, err := az.container.ListBlobsFlatSegment(
|
||||
+ context.Background(), marker, azblob.ListBlobsSegmentOptions{
|
||||
+ Prefix: prefix,
|
||||
+ MaxResults: 1,
|
||||
+ Details: azblob.BlobListingDetails{Metadata: true}})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
|
||||
}
|
||||
|
||||
- for _, blob := range page.Segment.BlobItems {
|
||||
- if prefix == "" {
|
||||
- paths = append(paths, *blob.Name)
|
||||
- } else {
|
||||
- name := *blob.Name
|
||||
- paths = append(paths, name[len(prefix):])
|
||||
- }
|
||||
- b := *blob
|
||||
- md5 := b.Properties.ContentMD5
|
||||
- md5s = append(md5s, fmt.Sprintf("%x", md5))
|
||||
+ marker = listBlob.NextMarker
|
||||
|
||||
+ for _, blob := range listBlob.Segment.BlobItems {
|
||||
+ if prefix == "" {
|
||||
+ paths = append(paths, blob.Name)
|
||||
+ } else {
|
||||
+ paths = append(paths, blob.Name[len(prefix):])
|
||||
+ }
|
||||
+ md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
|
||||
}
|
||||
+
|
||||
if progress != nil {
|
||||
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||
progress.AddBar(1)
|
||||
@@ -105,27 +97,28 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
||||
return paths, md5s, nil
|
||||
}
|
||||
|
||||
-func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error {
|
||||
- uploadOptions := &azblob.UploadFileOptions{
|
||||
- BlockSize: 4 * 1024 * 1024,
|
||||
- Concurrency: 8,
|
||||
+func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
|
||||
+ uploadOptions := azblob.UploadStreamToBlockBlobOptions{
|
||||
+ BufferSize: 4 * 1024 * 1024,
|
||||
+ MaxBuffers: 8,
|
||||
}
|
||||
|
||||
- path := az.blobPath(blobName)
|
||||
if len(sourceMD5) > 0 {
|
||||
decodedMD5, err := hex.DecodeString(sourceMD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
- uploadOptions.HTTPHeaders = &blob.HTTPHeaders{
|
||||
- BlobContentMD5: decodedMD5,
|
||||
+ uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
|
||||
+ ContentMD5: decodedMD5,
|
||||
}
|
||||
}
|
||||
|
||||
- var err error
|
||||
- if file, ok := source.(*os.File); ok {
|
||||
- _, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions)
|
||||
- }
|
||||
+ _, err := azblob.UploadStreamToBlockBlob(
|
||||
+ context.Background(),
|
||||
+ source,
|
||||
+ blob.ToBlockBlobURL(),
|
||||
+ uploadOptions,
|
||||
+ )
|
||||
|
||||
return err
|
||||
}
|
||||
diff --git a/azure/package_pool.go b/azure/package_pool.go
|
||||
index 97be8e6..6d7af1a 100644
|
||||
--- a/azure/package_pool.go
|
||||
+++ b/azure/package_pool.go
|
||||
@@ -5,6 +5,7 @@
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/pkg/errors"
|
||||
@@ -40,7 +41,10 @@ func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.Checksu
|
||||
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
|
||||
}
|
||||
|
||||
-func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) {
|
||||
+func (pool *PackagePool) ensureChecksums(
|
||||
+ poolPath string,
|
||||
+ checksumStorage aptly.ChecksumStorage,
|
||||
+) (*utils.ChecksumInfo, error) {
|
||||
targetChecksums, err := checksumStorage.Get(poolPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -48,7 +52,8 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
||||
|
||||
if targetChecksums == nil {
|
||||
// we don't have checksums stored yet for this file
|
||||
- download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil)
|
||||
+ blob := pool.az.blobURL(poolPath)
|
||||
+ download, err := blob.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
if isBlobNotFound(err) {
|
||||
return nil, nil
|
||||
@@ -58,7 +63,7 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
||||
}
|
||||
|
||||
targetChecksums = &utils.ChecksumInfo{}
|
||||
- *targetChecksums, err = utils.ChecksumsForReader(download.Body)
|
||||
+ *targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
|
||||
}
|
||||
@@ -87,49 +92,46 @@ func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, er
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Size(path string) (int64, error) {
|
||||
- serviceClient := pool.az.client.ServiceClient()
|
||||
- containerClient := serviceClient.NewContainerClient(pool.az.container)
|
||||
- blobClient := containerClient.NewBlobClient(path)
|
||||
-
|
||||
- props, err := blobClient.GetProperties(context.TODO(), nil)
|
||||
+ blob := pool.az.blobURL(path)
|
||||
+ props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||
}
|
||||
|
||||
- return *props.ContentLength, nil
|
||||
+ return props.ContentLength(), nil
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
||||
+ blob := pool.az.blobURL(path)
|
||||
+
|
||||
temp, err := os.CreateTemp("", "blob-download")
|
||||
if err != nil {
|
||||
- return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
|
||||
+ return nil, errors.Wrap(err, "error creating temporary file for blob download")
|
||||
}
|
||||
+
|
||||
defer func () { _ = os.Remove(temp.Name()) }()
|
||||
|
||||
- _, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
|
||||
+ err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
|
||||
if err != nil {
|
||||
- return nil, errors.Wrapf(err, "error downloading blob %s", path)
|
||||
+ return nil, errors.Wrapf(err, "error downloading blob at %s", path)
|
||||
}
|
||||
|
||||
return temp, nil
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Remove(path string) (int64, error) {
|
||||
- serviceClient := pool.az.client.ServiceClient()
|
||||
- containerClient := serviceClient.NewContainerClient(pool.az.container)
|
||||
- blobClient := containerClient.NewBlobClient(path)
|
||||
-
|
||||
- props, err := blobClient.GetProperties(context.TODO(), nil)
|
||||
+ blob := pool.az.blobURL(path)
|
||||
+ props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
- return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||
+ return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
|
||||
}
|
||||
|
||||
- _, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil)
|
||||
+ _, err = blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
|
||||
}
|
||||
|
||||
- return *props.ContentLength, nil
|
||||
+ return props.ContentLength(), nil
|
||||
}
|
||||
|
||||
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
|
||||
@@ -143,6 +145,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||
}
|
||||
|
||||
path := pool.buildPoolPath(basename, checksums)
|
||||
+ blob := pool.az.blobURL(path)
|
||||
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -158,7 +161,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||
}
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
- err = pool.az.putFile(path, source, checksums.MD5)
|
||||
+ err = pool.az.putFile(blob, source, checksums.MD5)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
diff --git a/azure/package_pool_test.go b/azure/package_pool_test.go
|
||||
index ef562cb..6b1341d 100644
|
||||
--- a/azure/package_pool_test.go
|
||||
+++ b/azure/package_pool_test.go
|
||||
@@ -7,7 +7,7 @@
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/files"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
@@ -50,10 +50,8 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
|
||||
|
||||
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||
c.Assert(err, IsNil)
|
||||
- publicAccessType := azblob.PublicAccessTypeContainer
|
||||
- _, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
|
||||
- Access: &publicAccessType,
|
||||
- })
|
||||
+ cnt := s.pool.az.container
|
||||
+ _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||
diff --git a/azure/public.go b/azure/public.go
|
||||
index 6775e14..efd2e7a 100644
|
||||
--- a/azure/public.go
|
||||
+++ b/azure/public.go
|
||||
@@ -3,22 +3,21 @@
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
+ "net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease"
|
||||
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
- "github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
||||
type PublishedStorage struct {
|
||||
// FIXME: unused ???? prefix string
|
||||
+ container azblob.ContainerURL
|
||||
az *azContext
|
||||
pathCache map[string]map[string]string
|
||||
}
|
||||
@@ -67,7 +66,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||
}
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
- err = storage.az.putFile(path, source, sourceMD5)
|
||||
+ err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
||||
}
|
||||
@@ -77,15 +76,14 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||
|
||||
// RemoveDirs removes directory structure under public path
|
||||
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
|
||||
- path = storage.az.blobPath(path)
|
||||
filelist, err := storage.Filelist(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, filename := range filelist {
|
||||
- blob := filepath.Join(path, filename)
|
||||
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil)
|
||||
+ blob := storage.az.blobURL(filepath.Join(path, filename))
|
||||
+ _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
||||
}
|
||||
@@ -96,8 +94,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error
|
||||
|
||||
// Remove removes single file under public path
|
||||
func (storage *PublishedStorage) Remove(path string) error {
|
||||
- path = storage.az.blobPath(path)
|
||||
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil)
|
||||
+ blob := storage.az.blobURL(path)
|
||||
+ _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
||||
}
|
||||
@@ -116,8 +114,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
||||
|
||||
relFilePath := filepath.Join(publishedRelPath, fileName)
|
||||
- prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||
- poolPath := storage.az.blobPath(prefixRelFilePath)
|
||||
+ // prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||
+ // FIXME: check how to integrate publishedPrefix:
|
||||
+ poolPath := storage.az.blobPath(fileName)
|
||||
|
||||
if storage.pathCache == nil {
|
||||
storage.pathCache = make(map[string]map[string]string)
|
||||
@@ -160,7 +159,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
}
|
||||
defer func() { _ = source.Close() }()
|
||||
|
||||
- err = storage.az.putFile(relFilePath, source, sourceMD5)
|
||||
+ err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
|
||||
if err == nil {
|
||||
pathCache[relFilePath] = sourceMD5
|
||||
} else {
|
||||
@@ -177,60 +176,59 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
||||
}
|
||||
|
||||
// Internal copy or move implementation
|
||||
-func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error {
|
||||
+func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
|
||||
const leaseDuration = 30
|
||||
- leaseID := uuid.NewString()
|
||||
|
||||
- serviceClient := storage.az.client.ServiceClient()
|
||||
- containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||
- srcBlobClient := containerClient.NewBlobClient(src)
|
||||
- blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)})
|
||||
- if err != nil {
|
||||
- return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||
- }
|
||||
-
|
||||
- _, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil)
|
||||
- if err != nil {
|
||||
- return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||
+ dstBlobURL := storage.az.blobURL(dst)
|
||||
+ srcBlobURL := storage.az.blobURL(src)
|
||||
+ leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
|
||||
+ if err != nil || leaseResp.StatusCode() != http.StatusCreated {
|
||||
+ return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
|
||||
}
|
||||
defer func() {
|
||||
- _, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
|
||||
+ _, _ = srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{})
|
||||
}()
|
||||
+ srcBlobLeaseID := leaseResp.LeaseID()
|
||||
|
||||
- dstBlobClient := containerClient.NewBlobClient(dst)
|
||||
- copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
|
||||
- Metadata: metadata,
|
||||
- })
|
||||
-
|
||||
+ copyResp, err := dstBlobURL.StartCopyFromURL(
|
||||
+ context.Background(),
|
||||
+ srcBlobURL.URL(),
|
||||
+ metadata,
|
||||
+ azblob.ModifiedAccessConditions{},
|
||||
+ azblob.BlobAccessConditions{},
|
||||
+ azblob.DefaultAccessTier,
|
||||
+ nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
|
||||
}
|
||||
|
||||
- copyStatus := *copyResp.CopyStatus
|
||||
+ copyStatus := copyResp.CopyStatus()
|
||||
for {
|
||||
- if copyStatus == blob.CopyStatusTypeSuccess {
|
||||
+ if copyStatus == azblob.CopyStatusSuccess {
|
||||
if move {
|
||||
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{
|
||||
- AccessConditions: &blob.AccessConditions{
|
||||
- LeaseAccessConditions: &blob.LeaseAccessConditions{
|
||||
- LeaseID: &leaseID,
|
||||
- },
|
||||
- },
|
||||
- })
|
||||
+ _, err = srcBlobURL.Delete(
|
||||
+ context.Background(),
|
||||
+ azblob.DeleteSnapshotsOptionNone,
|
||||
+ azblob.BlobAccessConditions{
|
||||
+ LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
|
||||
+ })
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
- } else if copyStatus == blob.CopyStatusTypePending {
|
||||
+ } else if copyStatus == azblob.CopyStatusPending {
|
||||
time.Sleep(1 * time.Second)
|
||||
- getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil)
|
||||
+ blobPropsResp, err := dstBlobURL.GetProperties(
|
||||
+ context.Background(),
|
||||
+ azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
|
||||
+ azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
- return fmt.Errorf("error getting copy progress %s", dst)
|
||||
+ return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
|
||||
}
|
||||
- copyStatus = *getMetadata.CopyStatus
|
||||
+ copyStatus = blobPropsResp.CopyStatus()
|
||||
|
||||
- _, err = blobLeaseClient.RenewLease(context.Background(), nil)
|
||||
+ _, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
|
||||
if err != nil {
|
||||
- return fmt.Errorf("error renewing source blob lease %s", src)
|
||||
+ return fmt.Errorf("error renewing source blob lease %s", srcBlobURL)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
|
||||
@@ -245,9 +243,7 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
|
||||
|
||||
// SymLink creates a copy of src file and adds link information as meta data
|
||||
func (storage *PublishedStorage) SymLink(src string, dst string) error {
|
||||
- metadata := make(map[string]*string)
|
||||
- metadata["SymLink"] = &src
|
||||
- return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */)
|
||||
+ return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
|
||||
}
|
||||
|
||||
// HardLink using symlink functionality as hard links do not exist
|
||||
@@ -257,33 +253,28 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
|
||||
|
||||
// FileExists returns true if path exists
|
||||
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
||||
- serviceClient := storage.az.client.ServiceClient()
|
||||
- containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||
- blobClient := containerClient.NewBlobClient(path)
|
||||
- _, err := blobClient.GetProperties(context.Background(), nil)
|
||||
+ blob := storage.az.blobURL(path)
|
||||
+ resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
if isBlobNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
- return false, fmt.Errorf("error checking if blob %s exists: %v", path, err)
|
||||
+ return false, err
|
||||
+ } else if resp.StatusCode() == http.StatusOK {
|
||||
+ return true, nil
|
||||
}
|
||||
- return true, nil
|
||||
+ return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||
}
|
||||
|
||||
// ReadLink returns the symbolic link pointed to by path.
|
||||
// This simply reads text file created with SymLink
|
||||
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
||||
- serviceClient := storage.az.client.ServiceClient()
|
||||
- containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||
- blobClient := containerClient.NewBlobClient(path)
|
||||
- props, err := blobClient.GetProperties(context.Background(), nil)
|
||||
+ blob := storage.az.blobURL(path)
|
||||
+ resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
if err != nil {
|
||||
- return "", fmt.Errorf("failed to get blob properties: %v", err)
|
||||
+ return "", err
|
||||
+ } else if resp.StatusCode() != http.StatusOK {
|
||||
+ return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||
}
|
||||
-
|
||||
- metadata := props.Metadata
|
||||
- if originalBlob, exists := metadata["original_blob"]; exists {
|
||||
- return *originalBlob, nil
|
||||
- }
|
||||
- return "", fmt.Errorf("error reading link %s: %v", path, err)
|
||||
+ return resp.NewMetadata()["SymLink"], nil
|
||||
}
|
||||
diff --git a/azure/public_test.go b/azure/public_test.go
|
||||
index 5c912c5..f58ad51 100644
|
||||
--- a/azure/public_test.go
|
||||
+++ b/azure/public_test.go
|
||||
@@ -7,11 +7,8 @@
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
- "bytes"
|
||||
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/aptly-dev/aptly/files"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
. "gopkg.in/check.v1"
|
||||
@@ -69,10 +66,8 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
||||
|
||||
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||
c.Assert(err, IsNil)
|
||||
- publicAccessType := azblob.PublicAccessTypeContainer
|
||||
- _, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
|
||||
- Access: &publicAccessType,
|
||||
- })
|
||||
+ cnt := s.storage.az.container
|
||||
+ _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||
@@ -80,39 +75,41 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
||||
- _, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
|
||||
+ cnt := s.storage.az.container
|
||||
+ _, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
||||
- resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
|
||||
+ blob := s.storage.az.container.NewBlobURL(path)
|
||||
+ resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||
c.Assert(err, IsNil)
|
||||
- data, err := io.ReadAll(resp.Body)
|
||||
+ body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
|
||||
+ data, err := io.ReadAll(body)
|
||||
c.Assert(err, IsNil)
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
||||
- serviceClient := s.storage.az.client.ServiceClient()
|
||||
- containerClient := serviceClient.NewContainerClient(s.storage.az.container)
|
||||
- blobClient := containerClient.NewBlobClient(path)
|
||||
- _, err := blobClient.GetProperties(context.Background(), nil)
|
||||
+ _, err := s.storage.az.container.NewBlobURL(path).GetProperties(
|
||||
+ context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||
c.Assert(err, NotNil)
|
||||
-
|
||||
- storageError, ok := err.(*azcore.ResponseError)
|
||||
+ storageError, ok := err.(azblob.StorageError)
|
||||
c.Assert(ok, Equals, true)
|
||||
- c.Assert(storageError.StatusCode, Equals, 404)
|
||||
+ c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
||||
hash := md5.Sum(data)
|
||||
- uploadOptions := &azblob.UploadStreamOptions{
|
||||
- HTTPHeaders: &blob.HTTPHeaders{
|
||||
- BlobContentMD5: hash[:],
|
||||
- },
|
||||
- }
|
||||
- reader := bytes.NewReader(data)
|
||||
- _, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
|
||||
+ _, err := azblob.UploadBufferToBlockBlob(
|
||||
+ context.Background(),
|
||||
+ data,
|
||||
+ s.storage.az.container.NewBlockBlobURL(path),
|
||||
+ azblob.UploadToBlockBlobOptions{
|
||||
+ BlobHTTPHeaders: azblob.BlobHTTPHeaders{
|
||||
+ ContentMD5: hash[:],
|
||||
+ },
|
||||
+ })
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
@@ -333,7 +330,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
|
||||
|
||||
// 2nd link from pool, providing wrong path for source file
|
||||
//
|
||||
- // this test should check that file already exists in Azure and skip upload (which would fail if not skipped)
|
||||
+ // this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
|
||||
s.prefixedStorage.pathCache = nil
|
||||
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
||||
c.Check(err, IsNil)
|
||||
diff --git a/context/context.go b/context/context.go
|
||||
index 0ffc3f7..503cad2 100644
|
||||
--- a/context/context.go
|
||||
+++ b/context/context.go
|
||||
@@ -100,6 +100,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
||||
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
|
||||
|
||||
for _, configLocation := range configLocations {
|
||||
+ // FIXME: check if exists, check if readable
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
if os.IsPermission(err) || os.IsNotExist(err) {
|
||||
continue
|
||||
diff --git a/deb/list.go b/deb/list.go
|
||||
index 25a2d28..9eda528 100644
|
||||
--- a/deb/list.go
|
||||
+++ b/deb/list.go
|
||||
@@ -598,6 +598,7 @@ func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
|
||||
//
|
||||
// when follow-all-variants is enabled, we need to try to expand anyway,
|
||||
// as even if dependency is satisfied now, there might be other ways to satisfy dependency
|
||||
+ // FIXME: do not search twice
|
||||
if result.Search(dep, false, true) != nil {
|
||||
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
|
||||
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
|
||||
diff --git a/go.mod b/go.mod
|
||||
index 53c5e78..d7f145a 100644
|
||||
--- a/go.mod
|
||||
+++ b/go.mod
|
||||
@@ -4,6 +4,7 @@ go 1.24
|
||||
|
||||
require (
|
||||
github.com/AlekSi/pointer v1.1.0
|
||||
+ github.com/Azure/azure-storage-blob-go v0.15.0
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55
|
||||
github.com/awalterschulze/gographviz v2.0.1+incompatible
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||
@@ -41,7 +42,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
+ github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
@@ -87,6 +88,7 @@ require (
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
+ github.com/mattn/go-ieproxy v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -96,7 +98,7 @@ require (
|
||||
github.com/prometheus/common v0.59.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
- github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
+ github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||
@@ -115,8 +117,6 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||
github.com/ProtonMail/go-crypto v1.0.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.5
|
||||
diff --git a/go.sum b/go.sum
|
||||
index 502f4b2..453a288 100644
|
||||
--- a/go.sum
|
||||
+++ b/go.sum
|
||||
@@ -1,17 +1,20 @@
|
||||
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
||||
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
|
||||
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
|
||||
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
+github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||
+github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||
+github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||
+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
+github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
|
||||
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
@@ -91,6 +94,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
@@ -127,8 +132,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
-github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
-github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -149,7 +152,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
@@ -197,6 +201,9 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||
+github.com/mattn/go-ieproxy v0.0.9 h1:RvVbLiMv/Hbjf1gRaC2AQyzwbdVhdId7D2vPnXIml4k=
|
||||
+github.com/mattn/go-ieproxy v0.0.9/go.mod h1:eF30/rfdQUO9EnzNIZQr0r9HiLMlZNCpJkHbmMuOAE0=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -235,8 +242,6 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -254,8 +259,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||
@@ -319,6 +324,8 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
@@ -333,14 +340,14 @@ golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
-golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
+golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
@@ -362,6 +369,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
From: =?utf-8?q?S=C3=A9bastien_Delafond?= <seb@debian.org>
|
||||
Date: Mon, 17 Feb 2025 10:11:55 +0100
|
||||
Subject: tests: no upstream's etcd install as it's arch-specific,
|
||||
and no swagger-related or modules tasks
|
||||
|
||||
Forwarded: not-needed
|
||||
---
|
||||
Makefile | 11 +++--------
|
||||
1 file changed, 3 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/Makefile b/Makefile
|
||||
index ffe2e8a..91f96a8 100644
|
||||
--- a/Makefile
|
||||
+++ b/Makefile
|
||||
@@ -89,17 +89,12 @@ install:
|
||||
# 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
|
||||
|
||||
-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"
|
||||
- @mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 &
|
||||
+test: ## Run unit tests
|
||||
@echo "\e[33m\e[1mRunning go test ...\e[0m"
|
||||
- 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"
|
||||
- @pid=`cat /tmp/etcd.pid`; kill $$pid
|
||||
- @rm -f /tmp/aptly-etcd-data/etcd.log
|
||||
+ go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
|
||||
@ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret
|
||||
|
||||
-system-test: prepare swagger etcd-install ## Run system tests
|
||||
+system-test: ## Run system tests
|
||||
# build coverage binary
|
||||
go test -v -coverpkg="./..." -c -tags testruncli
|
||||
# Download fixture-db, fixture-pool, etcd.db
|
||||
@@ -0,0 +1,40 @@
|
||||
From: =?utf-8?q?S=C3=A9bastien_Delafond?= <seb@debian.org>
|
||||
Date: Wed, 24 Sep 2025 07:23:24 +0200
|
||||
Subject: Revert "system-tests: abort on failure"
|
||||
|
||||
We'd rather have the test suite always run completely, and report
|
||||
every failed test at the end.
|
||||
|
||||
Forwarded: not-needed
|
||||
---
|
||||
system/run.py | 8 --------
|
||||
1 file changed, 8 deletions(-)
|
||||
|
||||
diff --git a/system/run.py b/system/run.py
|
||||
index 4e73fb2..599afe5 100755
|
||||
--- a/system/run.py
|
||||
+++ b/system/run.py
|
||||
@@ -50,7 +50,6 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
|
||||
if not coverage_dir:
|
||||
coverage_dir = mkdtemp(suffix="aptly-coverage")
|
||||
|
||||
- failed = False
|
||||
for test in tests:
|
||||
orig_stdout = sys.stdout
|
||||
orig_stderr = sys.stderr
|
||||
@@ -158,15 +157,8 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
|
||||
|
||||
t.shutdown()
|
||||
|
||||
- if failed:
|
||||
- break
|
||||
- if failed:
|
||||
- break
|
||||
-
|
||||
sys.stdout = orig_stdout
|
||||
sys.stderr = orig_stderr
|
||||
- if failed:
|
||||
- break
|
||||
|
||||
if lastBase is not None:
|
||||
lastBase.shutdown_class()
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
0001-disable-swagger.patch
|
||||
0002-disable-new-azure-sdk.patch
|
||||
0003-tests-no-upstream-s-etcd-install-as-it-s-arch-specif.patch
|
||||
0004-Revert-system-tests-abort-on-failure.patch
|
||||
Vendored
+4
-34
@@ -2,47 +2,17 @@
|
||||
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
export GOPATH=$(shell pwd)/.go
|
||||
export DEB_BUILD_OPTIONS=crossbuildcanrunhostbinaries
|
||||
|
||||
export GOARCH := $(shell if [ $(DEB_TARGET_ARCH) = "i386" ]; then echo "386"; elif [ $(DEB_TARGET_ARCH) = "armhf" ]; then echo "arm"; else echo $(DEB_TARGET_ARCH); fi)
|
||||
export CGO_ENABLED=1
|
||||
|
||||
ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE))
|
||||
export CC=$(DEB_HOST_GNU_TYPE)-gcc
|
||||
endif
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=golang --with=golang,bash-completion
|
||||
|
||||
override_dh_auto_clean:
|
||||
rm -rf build/
|
||||
rm -rf obj-$(DEB_TARGET_GNU_TYPE)/
|
||||
dh_auto_clean
|
||||
|
||||
override_dh_auto_test:
|
||||
# run during autopkgtests
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install -- --no-source
|
||||
|
||||
override_dh_strip:
|
||||
dh_strip --dbg-package=aptly-dbg
|
||||
|
||||
override_dh_golang: # fails on non native debian build
|
||||
|
||||
# override_dh_makeshlibs: # fails with cross compiling on non native debian build
|
||||
|
||||
override_dh_dwz: # somehow dwz works only with certain newer debhelper versions
|
||||
dhver=`dpkg-query -f '$${Version}' -W debhelper`; (dpkg --compare-versions "$$dhver" lt 13 || test "$$dhver" = "13.3.4" || test "$$dhver" = "13.6ubuntu1") || dh_dwz
|
||||
|
||||
override_dh_shlibdeps:
|
||||
ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE))
|
||||
LD_LIBRARY_PATH=/usr/$(DEB_HOST_GNU_TYPE)/lib:$$LD_LIBRARY_PATH dh_shlibdeps
|
||||
else
|
||||
dh_shlibdeps
|
||||
endif
|
||||
|
||||
override_dh_auto_build:
|
||||
echo $(DEB_VERSION) > VERSION
|
||||
go build -buildmode=pie -o usr/bin/aptly
|
||||
echo $(DEB_VERSION) > obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/VERSION
|
||||
mkdir -p obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian
|
||||
cp debian/aptly.conf obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian/
|
||||
dh_auto_build
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
3.0 (git)
|
||||
3.0 (quilt)
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
# run upstream's unit tests with Debian-supplied dependencies
|
||||
|
||||
# FIXME: right now this fails hard because many prerequisites are only
|
||||
# handled in `make test`
|
||||
|
||||
set -e
|
||||
set -u
|
||||
set -x
|
||||
|
||||
BUILD_DIR="${PWD}/_build"
|
||||
DH_OPTIONS="-O--buildsystem=golang -O--builddirectory=$BUILD_DIR"
|
||||
APTLY_DIR="${BUILD_DIR}/src/github.com/aptly-dev/aptly"
|
||||
|
||||
dpkg-source --before-build .
|
||||
|
||||
dh_update_autotools_config $DH_OPTIONS
|
||||
dh_autoreconf $DH_OPTIONS
|
||||
dh_auto_configure $DH_OPTIONS
|
||||
|
||||
dpkg-parsechangelog --show-field Version | perl -pe 's/\n//' >| ${APTLY_DIR}/VERSION
|
||||
find . -name VERSION
|
||||
|
||||
mkdir -p ${APTLY_DIR}/debian
|
||||
cp debian/aptly.conf ${APTLY_DIR}/debian/
|
||||
|
||||
dh_auto_build $DH_OPTIONS
|
||||
|
||||
export PATH=${BUILD_DIR}/bin:$PATH
|
||||
|
||||
dh_auto_test $DH_OPTIONS --no-parallel
|
||||
Vendored
+6
-3
@@ -1,4 +1,7 @@
|
||||
# This file is an addition to the autodep8 tests.
|
||||
|
||||
Test-Command: HOME=/tmp aptly repo create autopkgtest
|
||||
Tests: unit-test
|
||||
Depends: @, @builddeps@, ca-certificates, curl, git, gpg, gpg-agent
|
||||
Restrictions: allow-stderr
|
||||
|
||||
Tests: system-test
|
||||
Depends: @, @builddeps@, ca-certificates, curl, dirmngr, git, gpg, gpg-agent, gpgconf, graphviz, procps, python3, python3-requests-unixsocket, python3-termcolor, python3-swiftclient, python3-boto3, python3-azure-storage, python3-etcd3, python3-plyvel, sudo, zip
|
||||
Restrictions: allow-stderr, needs-internet
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eux
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
APTLY_SRC_DIR="${TMP_DIR}/src/github.com/aptly-dev/aptly"
|
||||
|
||||
# apply patches
|
||||
dpkg-source --before-build .
|
||||
|
||||
# copy source to GOPATH-compatible dir
|
||||
mkdir -p $(dirname $APTLY_SRC_DIR)
|
||||
cp -a . $APTLY_SRC_DIR
|
||||
|
||||
# not in a git tree, so we need to generate the version ourselves
|
||||
dpkg-parsechangelog --show-field Version | perl -pe 's/\n//' > ${APTLY_SRC_DIR}/VERSION
|
||||
|
||||
# use only apt-supplied go libraries
|
||||
export GOPATH="${TMP_DIR}:/usr/share/gocode"
|
||||
export GO111MODULE=off
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
|
||||
# run upstream's integration tests
|
||||
|
||||
set -eux
|
||||
|
||||
. debian/tests/setup
|
||||
|
||||
## env
|
||||
TESTS_DIR="${APTLY_SRC_DIR}/system"
|
||||
|
||||
## functions
|
||||
disable_test() {
|
||||
local file=${1}.py
|
||||
local name=$2
|
||||
local reason=$3
|
||||
|
||||
echo "${name}.skipTest = 'Debian autopkgtest: $reason'" >> ${TESTS_DIR}/${file}
|
||||
}
|
||||
|
||||
## main
|
||||
export USER=root # for t07/RootDirInaccessible
|
||||
|
||||
disable_test t01_version/version VersionTest "version"
|
||||
disable_test t02_config/config CreateConfigTest "different conf"
|
||||
disable_test t04_mirror/create CreateMirror31Test "public key not found"
|
||||
disable_test t04_mirror/create CreateMirror35Test "flaky on s390"
|
||||
disable_test t07_serve/serve Serve1Test "minor html diff"
|
||||
disable_test t09_repo/edit EditRepo4Test "flaky on riscv64"
|
||||
disable_test t10_task/run RunTask1Test "version"
|
||||
disable_test t12_api/docs TaskAPITestSwaggerDocs "no recent swag"
|
||||
disable_test t12_api/gpg GPGAPITestAddKey "flaky on s390"
|
||||
disable_test t12_api/unix_socket UnixSocketAPITest "type mismatch"
|
||||
disable_test t12_api/version VersionAPITest "type mismatch"
|
||||
disable_test t14_graph/graph CreateGraphTest "no viewer"
|
||||
disable_test t14_graph/graph CreateGraphOutputTest "no viewer"
|
||||
|
||||
# etcd fixture is entirely arch-specific
|
||||
rm -fr ${TESTS_DIR}/t13_etcd
|
||||
|
||||
make -C $APTLY_SRC_DIR system-test
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
# run upstream's unit tests with their full etcd fixtures, etc
|
||||
|
||||
set -eux
|
||||
|
||||
. debian/tests/setup
|
||||
|
||||
# FIXME: errors with non-constant format string in call to
|
||||
# github.com/aptly-dev/aptly/s3.fatalError
|
||||
rm ${APTLY_SRC_DIR}/s3/server_test.go
|
||||
rm ${APTLY_SRC_DIR}/s3/public_test.go
|
||||
|
||||
# upstream's etcd fixture is arch-specific
|
||||
rm ${APTLY_SRC_DIR}/database/etcddb/database_test.go
|
||||
|
||||
# TestVerifyClearsigned fails because of an extra signature
|
||||
perl -i -pe 's/(TestVerifyClearsigned)/No$1/' ${APTLY_SRC_DIR}/pgp/verify_test.go
|
||||
|
||||
make -C $APTLY_SRC_DIR test
|
||||
Vendored
+6
-5
@@ -1,5 +1,6 @@
|
||||
version=4
|
||||
opts=\
|
||||
repacksuffix=+ds1,\
|
||||
dversionmangle=s/\+ds\d*$// \
|
||||
https://github.com/aptly-dev/aptly/tags .*/v(\d[\d\.]*)\.tar\.gz debian uupdate
|
||||
Version: 5
|
||||
Template: Github
|
||||
Owner: aptly-dev
|
||||
Project: aptly
|
||||
Dversion-Mangle: s/\+ds\d*$//
|
||||
Repacksuffix: +ds1
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
|
||||
In order to add debian package files to a local repository, files are first uploaded to a temporary directory.
|
||||
Then the directory (or a specific file within) is added to a repository. After adding to a repositorty, the directory resp. files are removed bt default.
|
||||
Then the directory (or a specific file within) is added to a repository. After adding to a repository, the directory resp. files are removed bt default.
|
||||
|
||||
All uploaded files are stored under `<rootDir>/upload/<tempdir>` directory.
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Search Package Collection
|
||||
<div>
|
||||
Perform operations on the whole collection of packages in apty database.
|
||||
Perform operations on the whole collection of packages in aptly database.
|
||||
</div>
|
||||
|
||||
|
||||
+21
-2
@@ -11,11 +11,30 @@ Repositories can be published to local directories, Amazon S3 buckets, Azure or
|
||||
|
||||
GPG key is required to sign any published repository. The key pari should be generated before publishing.
|
||||
|
||||
Publiс part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
|
||||
Public part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
|
||||
|
||||
* Multiple signing keys can be defined in aptly.conf using the gpgKeys array:
|
||||
```
|
||||
"gpgKeys": [
|
||||
"KEY_ID_x",
|
||||
"KEY_ID_y"
|
||||
]
|
||||
```
|
||||
|
||||
* It is also possible to pass multiple keys via the CLI using the repeatable `--gpg-key` flag:
|
||||
```
|
||||
aptly publish repo my-repo --gpg-key=KEY_ID_a --gpg-key=KEY_ID_b
|
||||
```
|
||||
* When using the REST API, the `gpgKey` parameter supports a comma-separated list of key IDs:
|
||||
```
|
||||
"gpgKey": "KEY_ID_a,KEY_ID_b"
|
||||
```
|
||||
* If `--gpg-key` is specified on the command line, or `gpgKey` is provided via the REST API, it takes precedence over any gpgKeys configuration in aptly.conf.
|
||||
* With multi-key support, aptly will sign all Release files (both clearsigned and detached signatures) with each provided key, ensuring a smooth key rotation process while maintaining compatibility for existing clients.
|
||||
|
||||
#### Parameters
|
||||
|
||||
Publish APIs use following convention to identify published repositories: `/api/publish/:prefix/:distribution`. `:distribution` is distribution name, while `:prefix` is `[<storage>:]<prefix>` (storage is optional, it defaults to empty string), if publishing prefix contains slashes `/`, they should be replaced with underscores (`_`) and underscores
|
||||
should be replaced with double underscore (`__`). To specify root `:prefix`, use `:.`, as `.` is ambigious in URLs.
|
||||
should be replaced with double underscore (`__`). To specify root `:prefix`, use `:.`, as `.` is ambiguous in URLs.
|
||||
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Manage Local Repositories
|
||||
<div>
|
||||
A local repository is a collection of versionned packages (usually custom packages created internally).
|
||||
A local repository is a collection of versioned packages (usually custom packages created internally).
|
||||
|
||||
Packages can be added, removed, moved or copied between repos.
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = source.Close()
|
||||
_ = source.Close()
|
||||
}()
|
||||
|
||||
sourceInfo, err := source.Stat()
|
||||
|
||||
+23
-1
@@ -15,6 +15,10 @@ import (
|
||||
"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() }
|
||||
|
||||
// PublishedStorage abstract file system with public dirs (published repos)
|
||||
type PublishedStorage struct {
|
||||
rootPath string
|
||||
@@ -99,7 +103,17 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||
}()
|
||||
|
||||
_, err = io.Copy(f, source)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sync to ensure all data is written to disk and catch ENOSPC errors
|
||||
err = syncFile(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error syncing file %s: %s", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes single file under public path
|
||||
@@ -136,6 +150,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
|
||||
baseName := filepath.Base(fileName)
|
||||
poolPath := filepath.Join(storage.rootPath, publishedPrefix, publishedRelPath, filepath.Dir(fileName))
|
||||
destinationPath := filepath.Join(poolPath, baseName)
|
||||
|
||||
var localSourcePool aptly.LocalPackagePool
|
||||
if storage.linkMethod != LinkMethodCopy {
|
||||
@@ -242,6 +257,13 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
return err
|
||||
}
|
||||
|
||||
// Sync to ensure all data is written to disk and catch ENOSPC errors
|
||||
err = syncFile(dst)
|
||||
if err != nil {
|
||||
_ = dst.Close()
|
||||
return fmt.Errorf("error syncing file %s: %s", destinationPath, err)
|
||||
}
|
||||
|
||||
err = dst.Close()
|
||||
} else if storage.linkMethod == LinkMethodSymLink {
|
||||
err = localSourcePool.Symlink(sourcePath, filepath.Join(poolPath, baseName))
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
@@ -11,6 +17,77 @@ import (
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type fakeProgress struct{ bytes.Buffer }
|
||||
|
||||
func (p *fakeProgress) Start() {}
|
||||
func (p *fakeProgress) Shutdown() {}
|
||||
func (p *fakeProgress) Flush() {}
|
||||
func (p *fakeProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {
|
||||
}
|
||||
func (p *fakeProgress) ShutdownBar() {}
|
||||
func (p *fakeProgress) AddBar(count int) {}
|
||||
func (p *fakeProgress) SetBar(count int) {}
|
||||
func (p *fakeProgress) Printf(msg string, a ...interface{}) {
|
||||
}
|
||||
func (p *fakeProgress) ColoredPrintf(msg string, a ...interface{}) {
|
||||
}
|
||||
func (p *fakeProgress) PrintfStdErr(msg string, a ...interface{}) {
|
||||
}
|
||||
|
||||
type fakeRSC struct {
|
||||
*bytes.Reader
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func (r *fakeRSC) Close() error { return r.closeErr }
|
||||
|
||||
type fakePool struct {
|
||||
sizeErr error
|
||||
openFn func(string) (aptly.ReadSeekerCloser, error)
|
||||
}
|
||||
|
||||
type fakeLocalPool struct {
|
||||
fakePool
|
||||
statErr error
|
||||
}
|
||||
|
||||
func (p *fakeLocalPool) Stat(path string) (os.FileInfo, error) { return nil, p.statErr }
|
||||
func (p *fakeLocalPool) GenerateTempPath(filename string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (p *fakeLocalPool) Link(path, dstPath string) error { return nil }
|
||||
func (p *fakeLocalPool) Symlink(path, dstPath string) error { return nil }
|
||||
func (p *fakeLocalPool) FullPath(path string) string { return path }
|
||||
|
||||
func (p *fakePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
func (p *fakePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p *fakePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p *fakePool) Size(path string) (int64, error) {
|
||||
if p.sizeErr != nil {
|
||||
return 0, p.sizeErr
|
||||
}
|
||||
return int64(len(path)), nil
|
||||
}
|
||||
|
||||
func (p *fakePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
||||
if p.openFn != nil {
|
||||
return p.openFn(path)
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (p *fakePool) FilepathList(progress aptly.Progress) ([]string, error) { return nil, nil }
|
||||
func (p *fakePool) Remove(path string) (int64, error) { return 0, nil }
|
||||
|
||||
type PublishedStorageSuite struct {
|
||||
root string
|
||||
storage *PublishedStorage
|
||||
@@ -69,6 +146,14 @@ func (s *PublishedStorageSuite) TestPutFile(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestPutFileReturnsErrorIfSourceMissing(c *C) {
|
||||
err := s.storage.MkDir("ppa/dists/squeeze/")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.storage.PutFile("ppa/dists/squeeze/Release", filepath.Join(s.root, "no-such-file"))
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestFilelist(c *C) {
|
||||
err := s.storage.MkDir("ppa/pool/main/a/ab/")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -134,6 +219,11 @@ func (s *PublishedStorageSuite) TestSymLink(c *C) {
|
||||
c.Assert(linkTarget, Equals, "ppa/dists/squeeze/Release")
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestReadLinkReturnsErrorOnMissingPath(c *C) {
|
||||
_, err := s.storage.ReadLink("does/not/exist")
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestHardLink(c *C) {
|
||||
err := s.storage.MkDir("ppa/dists/squeeze/")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -163,6 +253,18 @@ func (s *PublishedStorageSuite) TestRemoveDirs(c *C) {
|
||||
c.Assert(os.IsNotExist(err), Equals, true)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestRemoveDirsWithProgress(c *C) {
|
||||
err := s.storage.MkDir("ppa/dists/squeeze/")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.storage.PutFile("ppa/dists/squeeze/Release", "/dev/null")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
p := &fakeProgress{}
|
||||
err = s.storage.RemoveDirs("ppa/dists/", p)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestRemove(c *C) {
|
||||
err := s.storage.MkDir("ppa/dists/squeeze/")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -337,3 +439,277 @@ func (s *PublishedStorageSuite) TestRootRemove(c *C) {
|
||||
dirStorage := NewPublishedStorage(pwd, "", "")
|
||||
c.Assert(func() { _ = dirStorage.RemoveDirs("", nil) }, PanicMatches, "trying to remove the root directory")
|
||||
}
|
||||
|
||||
// DiskFullSuite uses a loopback mount; requires Linux + root.
|
||||
|
||||
type DiskFullSuite struct {
|
||||
root string
|
||||
}
|
||||
|
||||
var _ = Suite(&DiskFullSuite{})
|
||||
|
||||
func (s *DiskFullSuite) SetUpTest(c *C) {
|
||||
if runtime.GOOS != "linux" {
|
||||
c.Skip("disk full tests only run on Linux")
|
||||
}
|
||||
|
||||
s.root = c.MkDir()
|
||||
}
|
||||
|
||||
func (s *DiskFullSuite) TestPutFileOutOfSpace(c *C) {
|
||||
mountPoint := "/smallfs"
|
||||
if os.Geteuid() == 0 {
|
||||
mountPoint = filepath.Join(s.root, "smallfs")
|
||||
err := os.MkdirAll(mountPoint, 0777)
|
||||
c.Assert(err, IsNil)
|
||||
fsImage := filepath.Join(s.root, "small.img")
|
||||
cmd := exec.Command("dd", "if=/dev/zero", "of="+fsImage, "bs=1M", "count=1")
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
cmd = exec.Command("mkfs.ext4", "-F", fsImage)
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
cmd = exec.Command("mount", "-o", "loop", fsImage, mountPoint)
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
defer func() {
|
||||
_ = exec.Command("umount", mountPoint).Run()
|
||||
}()
|
||||
}
|
||||
|
||||
storage := NewPublishedStorage(mountPoint, "", "")
|
||||
largeFile := filepath.Join(s.root, "largefile")
|
||||
cmd := exec.Command("dd", "if=/dev/zero", "of="+largeFile, "bs=1M", "count=2")
|
||||
err := cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = storage.PutFile("testfile", largeFile)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(strings.Contains(err.Error(), "no space left on device") ||
|
||||
strings.Contains(err.Error(), "sync"), Equals, true,
|
||||
Commentf("Expected disk full error, got: %v", err))
|
||||
}
|
||||
|
||||
func (s *DiskFullSuite) TestLinkFromPoolCopyOutOfSpace(c *C) {
|
||||
mountPoint := "/smallfs"
|
||||
if os.Geteuid() == 0 {
|
||||
mountPoint = filepath.Join(s.root, "smallfs")
|
||||
err := os.MkdirAll(mountPoint, 0777)
|
||||
c.Assert(err, IsNil)
|
||||
fsImage := filepath.Join(s.root, "small.img")
|
||||
|
||||
cmd := exec.Command("dd", "if=/dev/zero", "of="+fsImage, "bs=1M", "count=1")
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cmd = exec.Command("mkfs.ext4", "-F", fsImage)
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cmd = exec.Command("mount", "-o", "loop", fsImage, mountPoint)
|
||||
err = cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
defer func() {
|
||||
_ = exec.Command("umount", mountPoint).Run()
|
||||
}()
|
||||
}
|
||||
|
||||
storage := NewPublishedStorage(mountPoint, "copy", "")
|
||||
|
||||
poolPath := filepath.Join(s.root, "pool")
|
||||
pool := NewPackagePool(poolPath, false)
|
||||
cs := NewMockChecksumStorage()
|
||||
|
||||
largeFile := filepath.Join(s.root, "package.deb")
|
||||
cmd := exec.Command("dd", "if=/dev/zero", "of="+largeFile, "bs=1M", "count=2")
|
||||
err := cmd.Run()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
sourceChecksum, err := utils.ChecksumsForFile(largeFile)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
srcPoolPath, err := pool.Import(largeFile, "package.deb",
|
||||
&utils.ChecksumInfo{MD5: "d41d8cd98f00b204e9800998ecf8427e"}, false, cs)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = storage.LinkFromPool("", "pool/main/p/package", "package.deb",
|
||||
pool, srcPoolPath, sourceChecksum, false)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(strings.Contains(err.Error(), "no space left on device") ||
|
||||
strings.Contains(err.Error(), "sync"), Equals, true,
|
||||
Commentf("Expected disk full error, got: %v", err))
|
||||
}
|
||||
|
||||
type DiskFullNoRootSuite struct {
|
||||
root string
|
||||
}
|
||||
|
||||
var _ = Suite(&DiskFullNoRootSuite{})
|
||||
|
||||
func (s *DiskFullNoRootSuite) SetUpTest(c *C) {
|
||||
s.root = c.MkDir()
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestSyncIsCalled(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "", "")
|
||||
sourceFile := filepath.Join(s.root, "source.txt")
|
||||
err := os.WriteFile(sourceFile, []byte("test content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
err = storage.PutFile("dest.txt", sourceFile)
|
||||
c.Assert(err, IsNil)
|
||||
content, err := os.ReadFile(filepath.Join(s.root, "dest.txt"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(string(content), Equals, "test content")
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopySyncIsCalled(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "copy", "")
|
||||
poolPath := filepath.Join(s.root, "pool")
|
||||
pool := NewPackagePool(poolPath, false)
|
||||
cs := NewMockChecksumStorage()
|
||||
|
||||
pkgFile := filepath.Join(s.root, "package.deb")
|
||||
err := os.WriteFile(pkgFile, []byte("package content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
sourceChecksum, err := utils.ChecksumsForFile(pkgFile)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
srcPoolPath, err := pool.Import(pkgFile, "package.deb",
|
||||
&utils.ChecksumInfo{MD5: "d41d8cd98f00b204e9800998ecf8427e"}, false, cs)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = storage.LinkFromPool("", "pool/main/p/package", "package.deb",
|
||||
pool, srcPoolPath, sourceChecksum, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
destPath := filepath.Join(s.root, "pool/main/p/package/package.deb")
|
||||
content, err := os.ReadFile(destPath)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(string(content), Equals, "package content")
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestPutFileSyncErrorIsReturned(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "", "")
|
||||
|
||||
sourceFile := filepath.Join(s.root, "source-syncfail.txt")
|
||||
err := os.WriteFile(sourceFile, []byte("test content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
oldSyncFile := syncFile
|
||||
syncFile = func(_ *os.File) error { return syscall.ENOSPC }
|
||||
defer func() { syncFile = oldSyncFile }()
|
||||
|
||||
err = storage.PutFile("dest-syncfail.txt", sourceFile)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopySyncErrorIsReturned(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "copy", "")
|
||||
poolPath := filepath.Join(s.root, "pool")
|
||||
pool := NewPackagePool(poolPath, false)
|
||||
cs := NewMockChecksumStorage()
|
||||
|
||||
pkgFile := filepath.Join(s.root, "package-syncfail.deb")
|
||||
err := os.WriteFile(pkgFile, []byte("package content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
sourceChecksum, err := utils.ChecksumsForFile(pkgFile)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
srcPoolPath, err := pool.Import(pkgFile, "package-syncfail.deb",
|
||||
&utils.ChecksumInfo{MD5: "d41d8cd98f00b204e9800998ecf8427e"}, false, cs)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
oldSyncFile := syncFile
|
||||
syncFile = func(_ *os.File) error { return syscall.ENOSPC }
|
||||
defer func() { syncFile = oldSyncFile }()
|
||||
|
||||
err = storage.LinkFromPool("", "pool/main/p/package", "package-syncfail.deb",
|
||||
pool, srcPoolPath, sourceChecksum, false)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestPutFileFailsIfDestinationDirMissing(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "", "")
|
||||
|
||||
sourceFile := filepath.Join(s.root, "src.txt")
|
||||
err := os.WriteFile(sourceFile, []byte("x"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = storage.PutFile("missingdir/dest.txt", sourceFile)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolRejectsNonLocalPoolForHardlink(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "", "")
|
||||
pool := &fakePool{}
|
||||
|
||||
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(strings.Contains(err.Error(), "cannot link"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyReturnsErrorIfOpenFails(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "copy", "")
|
||||
pool := &fakePool{openFn: func(string) (aptly.ReadSeekerCloser, error) { return nil, io.ErrUnexpectedEOF }}
|
||||
|
||||
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyReturnsErrorIfReaderCloseFails(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "copy", "")
|
||||
|
||||
pool := &fakePool{openFn: func(string) (aptly.ReadSeekerCloser, error) {
|
||||
return &fakeRSC{Reader: bytes.NewReader([]byte("data")), closeErr: io.ErrClosedPipe}, nil
|
||||
}}
|
||||
|
||||
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(err, Equals, io.ErrClosedPipe)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyReturnsErrorIfSizeFailsWhenDestExists(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "copy", "size")
|
||||
pool := &fakePool{sizeErr: io.ErrUnexpectedEOF, openFn: func(string) (aptly.ReadSeekerCloser, error) {
|
||||
return &fakeRSC{Reader: bytes.NewReader([]byte("data")), closeErr: nil}, nil
|
||||
}}
|
||||
|
||||
destDir := filepath.Join(s.root, "pool/main/p/pkg")
|
||||
c.Assert(os.MkdirAll(destDir, 0777), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(destDir, "x.deb"), []byte("old"), 0644), IsNil)
|
||||
|
||||
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(err, Equals, io.ErrUnexpectedEOF)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyChecksumReturnsErrorIfDstMD5Fails(c *C) {
|
||||
storage := NewPublishedStorage(s.root, "copy", "")
|
||||
pool := &fakePool{openFn: func(string) (aptly.ReadSeekerCloser, error) {
|
||||
return &fakeRSC{Reader: bytes.NewReader([]byte("data")), closeErr: nil}, nil
|
||||
}}
|
||||
|
||||
// Make destinationPath a directory so MD5ChecksumForFile fails.
|
||||
destDir := filepath.Join(s.root, "pool/main/p/pkg")
|
||||
c.Assert(os.MkdirAll(destDir, 0777), IsNil)
|
||||
c.Assert(os.MkdirAll(filepath.Join(destDir, "x.deb"), 0777), IsNil)
|
||||
|
||||
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *DiskFullNoRootSuite) TestLinkFromPoolHardlinkReturnsErrorIfStatFailsWhenDestExists(c *C) {
|
||||
storage := NewPublishedStorage(c.MkDir(), "hardlink", "")
|
||||
pool := &fakeLocalPool{statErr: errors.New("stat failed")}
|
||||
|
||||
destDir := filepath.Join(storage.rootPath, "pool", "main", "p", "pkg")
|
||||
c.Assert(os.MkdirAll(destDir, 0777), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(destDir, "x.deb"), []byte("x"), 0644), IsNil)
|
||||
|
||||
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module github.com/aptly-dev/aptly
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/AlekSi/pointer v1.1.0
|
||||
@@ -32,29 +34,29 @@ require (
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
||||
github.com/ugorji/go/codec v1.2.11
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/term v0.30.0
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
|
||||
@@ -62,7 +64,7 @@ require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cloudflare/circl v1.4.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
@@ -87,6 +89,7 @@ require (
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -103,10 +106,10 @@ require (
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
@@ -115,17 +118,14 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||
github.com/ProtonMail/go-crypto v1.0.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.5
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0
|
||||
github.com/ProtonMail/go-crypto v1.4.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.5
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.46
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1
|
||||
github.com/aws/smithy-go v1.22.1
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
|
||||
github.com/aws/smithy-go v1.24.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
go.etcd.io/etcd/client/v3 v3.5.15
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
@@ -1,68 +1,70 @@
|
||||
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
||||
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
|
||||
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/awalterschulze/gographviz v2.0.1+incompatible h1:XIECBRq9VPEQqkQL5pw2OtjCAdrtIgFKoJU8eT98AS8=
|
||||
github.com/awalterschulze/gographviz v2.0.1+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg=
|
||||
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
|
||||
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
@@ -78,9 +80,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
|
||||
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
@@ -91,14 +92,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
@@ -127,8 +128,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -150,6 +149,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
@@ -197,6 +197,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -235,8 +237,6 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -284,10 +284,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
||||
@@ -300,7 +296,6 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
||||
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
|
||||
@@ -319,49 +314,41 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -377,30 +364,21 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -408,10 +386,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
+1
-1
@@ -240,7 +240,7 @@ func (downloader *downloaderImpl) download(req *http.Request, url, destination s
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !go1.7
|
||||
// +build !go1.7
|
||||
|
||||
package http
|
||||
|
||||
+11
-11
@@ -49,9 +49,9 @@ func (d *GrabDownloader) Download(ctx context.Context, url string, destination s
|
||||
|
||||
func (d *GrabDownloader) DownloadWithChecksum(ctx context.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error {
|
||||
maxTries := d.maxTries
|
||||
// FIXME: const delayMax = time.Duration(5 * time.Minute)
|
||||
// FIXME: const delayMax = time.Duration(5 * time.Minute)
|
||||
delay := time.Duration(1 * time.Second)
|
||||
// FIXME: const delayMultiplier = 2
|
||||
// FIXME: const delayMultiplier = 2
|
||||
err := fmt.Errorf("no tries available")
|
||||
for maxTries > 0 {
|
||||
err = d.download(ctx, url, destination, expected, ignoreMismatch)
|
||||
@@ -133,17 +133,17 @@ func (d *GrabDownloader) download(_ context.Context, url string, destination str
|
||||
|
||||
resp := d.client.Do(req)
|
||||
|
||||
<-resp.Done
|
||||
<-resp.Done
|
||||
// download is complete
|
||||
|
||||
// Loop:
|
||||
// for {
|
||||
// select {
|
||||
// case <-resp.Done:
|
||||
// // download is complete
|
||||
// break Loop
|
||||
// }
|
||||
// }
|
||||
// Loop:
|
||||
// for {
|
||||
// select {
|
||||
// case <-resp.Done:
|
||||
// // download is complete
|
||||
// break Loop
|
||||
// }
|
||||
// }
|
||||
err = resp.Err()
|
||||
if err != nil && err == grab.ErrBadChecksum && ignoreMismatch {
|
||||
fmt.Printf("Ignoring checksum mismatch for %s\n", url)
|
||||
|
||||
+5
-1
@@ -111,8 +111,9 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Enable metrics for Prometheus client
|
||||
"enableMetricsEndpoint": false,
|
||||
|
||||
// Not implemented in this version\.
|
||||
// Enable API documentation on /docs
|
||||
"enableSwaggerEndpoint": false,
|
||||
//"enableSwaggerEndpoint": false,
|
||||
|
||||
// OBSOLETE: use via url param ?_async=true
|
||||
"AsyncAPI": false,
|
||||
@@ -2452,6 +2453,9 @@ show yaml config
|
||||
.SH "ENVIRONMENT"
|
||||
If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\.
|
||||
.
|
||||
.P
|
||||
If environment variable \fBSOURCE_DATE_EPOCH\fR is set to a Unix timestamp, \fBaptly\fR would use that timestamp for the \fBDate\fR and \fBValid\-Until\fR fields in the \fBRelease\fR file when publishing\. This enables reproducible builds as specified by \fIhttps://reproducible\-builds\.org/specs/source\-date\-epoch/\fR\.
|
||||
.
|
||||
.SH "RETURN VALUES"
|
||||
\fBaptly\fR exists with:
|
||||
.
|
||||
|
||||
@@ -100,8 +100,9 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Enable metrics for Prometheus client
|
||||
"enableMetricsEndpoint": false,
|
||||
|
||||
// Not implemented in this version.
|
||||
// Enable API documentation on /docs
|
||||
"enableSwaggerEndpoint": false,
|
||||
//"enableSwaggerEndpoint": false,
|
||||
|
||||
// OBSOLETE: use via url param ?_async=true
|
||||
"AsyncAPI": false,
|
||||
@@ -533,6 +534,11 @@ For example, default aptly display format could be presented with the following
|
||||
If environment variable `HTTP_PROXY` is set `aptly` would use its value
|
||||
to proxy all HTTP requests.
|
||||
|
||||
If environment variable `SOURCE_DATE_EPOCH` is set to a Unix timestamp,
|
||||
`aptly` would use that timestamp for the `Date` and `Valid-Until` fields
|
||||
in the `Release` file when publishing. This enables reproducible builds
|
||||
as specified by https://reproducible-builds.org/specs/source-date-epoch/.
|
||||
|
||||
## RETURN VALUES
|
||||
|
||||
`aptly` exists with:
|
||||
|
||||
+11
-4
@@ -22,7 +22,7 @@ var (
|
||||
type GpgSigner struct {
|
||||
gpg string
|
||||
version GPGVersion
|
||||
keyRef string
|
||||
keyRefs []string
|
||||
keyring, secretKeyring string
|
||||
passphrase, passphraseFile string
|
||||
batch bool
|
||||
@@ -35,7 +35,14 @@ func (g *GpgSigner) SetBatch(batch bool) {
|
||||
|
||||
// SetKey sets key ID to use when signing files
|
||||
func (g *GpgSigner) SetKey(keyRef string) {
|
||||
g.keyRef = keyRef
|
||||
keyRef = strings.TrimSpace(keyRef)
|
||||
if keyRef != "" {
|
||||
if g.keyRefs == nil {
|
||||
g.keyRefs = []string{keyRef}
|
||||
} else {
|
||||
g.keyRefs = append(g.keyRefs, keyRef)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeyRing allows to set custom keyring and secretkeyring
|
||||
@@ -57,8 +64,8 @@ func (g *GpgSigner) gpgArgs() []string {
|
||||
args = append(args, "--secret-keyring", g.secretKeyring)
|
||||
}
|
||||
|
||||
if g.keyRef != "" {
|
||||
args = append(args, "-u", g.keyRef)
|
||||
for _, k := range g.keyRefs {
|
||||
args = append(args, "-u", k)
|
||||
}
|
||||
|
||||
if g.passphrase != "" || g.passphraseFile != "" {
|
||||
|
||||
+1
-1
@@ -346,7 +346,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||
storage.pathCache = make(map[string]string, len(paths))
|
||||
|
||||
for i := range paths {
|
||||
storage.pathCache[filepath.Join("pool", paths[i])] = md5s[i]
|
||||
storage.pathCache[filepath.Join(publishedPrefix, "pool", paths[i])] = md5s[i]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -112,9 +112,11 @@ func NewServer(config *Config) (*Server, error) {
|
||||
buckets: make(map[string]*bucket),
|
||||
config: config,
|
||||
}
|
||||
go func() { _ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
srv.serveHTTP(w, req)
|
||||
})) }()
|
||||
go func() {
|
||||
_ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
srv.serveHTTP(w, req)
|
||||
}))
|
||||
}()
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
@@ -527,14 +529,13 @@ func (bucketResource) post(a *action) interface{} {
|
||||
// and dashes (-). You can use uppercase letters for buckets only in the
|
||||
// US Standard region.
|
||||
//
|
||||
// Must start with a number or letter
|
||||
// # Must start with a number or letter
|
||||
//
|
||||
// Must be between 3 and 255 characters long
|
||||
// # Must be between 3 and 255 characters long
|
||||
//
|
||||
// There's one extra rule (Must not be formatted as an IP address (e.g., 192.168.5.4)
|
||||
// but the real S3 server does not seem to check that rule, so we will not
|
||||
// check it either.
|
||||
//
|
||||
func validBucketName(name string) bool {
|
||||
if len(name) < 3 || len(name) > 255 {
|
||||
return false
|
||||
|
||||
@@ -34,6 +34,7 @@ class APITest(BaseTest):
|
||||
"linkMethod": "symlink"
|
||||
}
|
||||
},
|
||||
"enableMetricsEndpoint": True,
|
||||
"enableSwaggerEndpoint": True
|
||||
}
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ else
|
||||
fi
|
||||
|
||||
cd /work/src
|
||||
sudo -u aptly PATH=$PATH:/work/src/build GOPATH=/work/src/.go $cmd
|
||||
sudo -u aptly PATH=$PATH:/work/src/build GOPATH=/work/src/.go GOCACHE=/work/src/.go/cache $cmd
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQGiBFL7pY8RBAC5uHg/9AuGJ7EF7RYty89IDLeqvlPe710eDQpJ+itsOaA/5rr3
|
||||
IV1LMlqHpM2rkZkAPpARwjrga2ByJ1ww77Zq2uPqJIO2LZYWTLXic9Zity2OVu3Z
|
||||
XwtdsqagIMfT5dAgNmhe5lL7qgGUwYcFFa52s7U4qO0z2FfwHW1IQrnMpwCg5RQh
|
||||
Uqs5iUKdDtoeQjX5mWgQhjEEAI1zfXUvvcOrRsDlGNKYZigZiWC6J46jeR8Nnf9C
|
||||
WwhXS2fzQaJyDq9DorkvPZgWUAaLLCdfGETqLzDKajynhS1+OnfFQNzvkvEPRBSb
|
||||
C5k+GOF2E1E9rGXb31+1XZTcdIprp4/F3RNLLWNUwfgPLWJx9NzHTYqgBStecHkC
|
||||
ySZRA/9PNFAbeJZ27HNuzoGnAa0piZDLeAAHsM1V6cosMh7U1IZqjZcrMC9YXNxH
|
||||
2D90PvoBvpufCMRzL/fOVPT1JzQGYoKIX17Nmzvdq/a4YyLWRODjvWXd94bae2Xd
|
||||
Vy03DYhfp8VOVJW6HuAX9JN6MKXSNxaibgOPjU822Hxd1iCIQ7QtQXB0bHkgVGVz
|
||||
dGVyIChkb24ndCB1c2UgaXQpIDx0ZXN0QGFwdGx5LmluZm8+iGIEExECACIFAlL7
|
||||
pY8CGyMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECHbuJwW2z5t2sQAoNn+
|
||||
0cADZa66HZNY2qJi44Oq4hjaAJsHzj9JKAHEpdix5N7b6QvaZQZYhrkBDQRS+6WP
|
||||
EAQA9BX+kbIM6VJYoyY9vUHXfAF4E2y2M7vl9knZ+jMPfMbI7dE3gRJQb3mngST5
|
||||
7eZWawo1DNE6h3LbHsB4mpro9XLUXUMBgXRsOq4D5E0ygvDZ/tJhy0AwFiTOXKEs
|
||||
/erzmbF7j/TWh4LVHXFI9DrnN0+EeF/mQC/wzX7WGCKe70cAAwUEAMr7959zUYNp
|
||||
E3v4IquIJpD22bT/FiyQjFG8yGy36c+7mOP3VWi0lz5yFqqeR9NDFuLDSwOEi0nB
|
||||
zXNmimLy+hIwMaHjbQLjLODmy/T9wKCgeAmK1ygT6YBGJJflThZ05M80T5hBtRA9
|
||||
z2eoTn0wbi6MLmD/rbEt+lUPfSA4V0t2iEkEGBECAAkFAlL7pY8CGwwACgkQIdu4
|
||||
nBbbPm05hgCgvYatZXRbEdZ91jJCQi1KI7lJ5Y8AnjvrHU0g84mE45QZFegZzzQo
|
||||
9relmDMEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqz
|
||||
Y8yWdcj3HUy0L0FwdGx5IFNlY29uZGFyeSBTaWduaW5nIEtleSA8YXB0bHlAZXhh
|
||||
bXBsZS5jb20+iJYEExYKAD4WIQSu4W3wGDVPZ/5fXHK79OGUNOkeTgUCZ3YCRgIb
|
||||
AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC79OGUNOkeTid/AP9A
|
||||
kIMn2qI5TqZgzrnPt7SN16VvpMppPb2H0m0P6knQKQD8DHcLcrqAl2cjcEuntv75
|
||||
gOnEvmPDAO6S1rc8UgcWdQQ=
|
||||
=XPoo
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
lFgEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqzY8yW
|
||||
dcj3HUwAAP9lsZgE1YQfaS9xfVOSi3f91lbq13+U9FPdwxfiET0+bBFrtC9BcHRs
|
||||
eSBTZWNvbmRhcnkgU2lnbmluZyBLZXkgPGFwdGx5QGV4YW1wbGUuY29tPoiWBBMW
|
||||
CgA+FiEEruFt8Bg1T2f+X1xyu/ThlDTpHk4FAmd2AkYCGwMFCQPCZwAFCwkIBwIG
|
||||
FQoJCAsCBBYCAwECHgECF4AACgkQu/ThlDTpHk4nfwD/QJCDJ9qiOU6mYM65z7e0
|
||||
jdelb6TKaT29h9JtD+pJ0CkA/Ax3C3K6gJdnI3BLp7b++YDpxL5jwwDukta3PFIH
|
||||
FnUE
|
||||
=IXTY
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
@@ -20,7 +20,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening DB %q: %s", dbPath, err)
|
||||
}
|
||||
defer db.Close()
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
keys := db.KeysByPrefix([]byte(prefix))
|
||||
if len(keys) == 0 {
|
||||
|
||||
+8
-3
@@ -272,6 +272,9 @@ class BaseTest(object):
|
||||
self.run_cmd([
|
||||
self.gpgFinder.gpg2, "--import",
|
||||
os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly.sec"], expected_code=None)
|
||||
self.run_cmd([
|
||||
self.gpgFinder.gpg2, "--import",
|
||||
os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly3.sec"], expected_code=None)
|
||||
|
||||
if self.fixtureGpg:
|
||||
self.run_cmd([self.gpgFinder.gpg, "--no-default-keyring", "--trust-model", "always", "--batch", "--keyring", "aptlytest.gpg", "--import"] +
|
||||
@@ -310,7 +313,9 @@ class BaseTest(object):
|
||||
|
||||
if command[0] == "aptly":
|
||||
aptly_testing_bin = Path(__file__).parent / ".." / "aptly.test"
|
||||
command = [str(aptly_testing_bin), f"-test.coverprofile={Path(self.coverage_dir) / self.__class__.__name__}-{uuid4()}.out", *command[1:]]
|
||||
command = [str(aptly_testing_bin), *command[1:]]
|
||||
if self.coverage_dir is not None:
|
||||
command.insert(1, f"-test.coverprofile={Path(self.coverage_dir) / self.__class__.__name__}-{uuid4()}.out")
|
||||
|
||||
if self.faketime:
|
||||
command = ["faketime", os.environ.get("TEST_FAKETIME", "2025-01-02 03:04:05")] + command
|
||||
@@ -337,7 +342,7 @@ class BaseTest(object):
|
||||
if is_aptly_command:
|
||||
# remove the last two rows as go tests always print PASS/FAIL and coverage in those
|
||||
# two lines. This would otherwise fail the tests as they would not match gold
|
||||
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output)
|
||||
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*(?:\ncoverage: .*|$)", raw_output)
|
||||
if not matches:
|
||||
raise Exception("no matches found in command output '%s'" % raw_output)
|
||||
|
||||
@@ -517,7 +522,7 @@ class BaseTest(object):
|
||||
if gold != output:
|
||||
diff = "".join(difflib.unified_diff(
|
||||
[l + "\n" for l in gold.split("\n")], [l + "\n" for l in output.split("\n")]))
|
||||
raise Exception("content doesn't match:\n" + diff + "\n\nOutput:\n" + orig + "\n")
|
||||
raise Exception(f"content doesn't match:\n{diff}\n\nOutput:\n{orig}\n")
|
||||
|
||||
check = check_output
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
azure-storage-blob
|
||||
boto
|
||||
requests==2.28.2
|
||||
requests==2.33.0
|
||||
requests-unixsocket
|
||||
python-swiftclient
|
||||
flake8
|
||||
|
||||
+6
-3
@@ -36,7 +36,7 @@ def natural_key(string_):
|
||||
return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]
|
||||
|
||||
|
||||
def run(include_long_tests=False, capture_results=False, tests=None, filters=None, coverage_dir=None):
|
||||
def run(include_long_tests=False, capture_results=False, tests=None, filters=None, coverage_dir=None, coverage_skip=False):
|
||||
"""
|
||||
Run system test.
|
||||
"""
|
||||
@@ -47,7 +47,7 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
|
||||
fails = []
|
||||
numTests = numFailed = numSkipped = 0
|
||||
lastBase = None
|
||||
if not coverage_dir:
|
||||
if not coverage_dir and not coverage_skip:
|
||||
coverage_dir = mkdtemp(suffix="aptly-coverage")
|
||||
|
||||
failed = False
|
||||
@@ -213,6 +213,7 @@ if __name__ == "__main__":
|
||||
include_long_tests = False
|
||||
capture_results = False
|
||||
coverage_dir = None
|
||||
coverage_skip = False
|
||||
tests = None
|
||||
args = sys.argv[1:]
|
||||
|
||||
@@ -224,6 +225,8 @@ if __name__ == "__main__":
|
||||
elif args[0] == "--coverage-dir":
|
||||
coverage_dir = args[1]
|
||||
args = args[1:]
|
||||
elif args[0] == "--coverage-skip":
|
||||
coverage_skip = True
|
||||
|
||||
args = args[1:]
|
||||
|
||||
@@ -236,4 +239,4 @@ if __name__ == "__main__":
|
||||
else:
|
||||
filters.append(arg)
|
||||
|
||||
run(include_long_tests, capture_results, tests, filters, coverage_dir)
|
||||
run(include_long_tests, capture_results, tests, filters, coverage_dir, coverage_skip)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencyVerboseResolve": false,
|
||||
"ppaDistributorID": "ubuntu",
|
||||
"ppaCodename": "",
|
||||
"ppaBaseURL": "http://ppa.launchpad.net",
|
||||
"serveInAPIMode": true,
|
||||
"enableMetricsEndpoint": true,
|
||||
"enableSwaggerEndpoint": false,
|
||||
@@ -29,6 +30,7 @@
|
||||
"gpgProvider": "gpg",
|
||||
"gpgDisableSign": false,
|
||||
"gpgDisableVerify": false,
|
||||
"gpgKeys": [],
|
||||
"skipContentsPublishing": false,
|
||||
"skipBz2Publishing": false,
|
||||
"FileSystemPublishEndpoints": {},
|
||||
|
||||
@@ -11,6 +11,7 @@ dep_follow_source: false
|
||||
dep_verboseresolve: false
|
||||
ppa_distributor_id: ubuntu
|
||||
ppa_codename: ""
|
||||
ppa_baseurl: http://ppa.launchpad.net
|
||||
serve_in_api_mode: true
|
||||
enable_metrics_endpoint: true
|
||||
enable_swagger_endpoint: false
|
||||
@@ -27,6 +28,7 @@ download_sourcepackages: false
|
||||
gpg_provider: gpg
|
||||
gpg_disable_sign: false
|
||||
gpg_disable_verify: false
|
||||
gpg_keys: []
|
||||
skip_contents_publishing: false
|
||||
skip_bz2_publishing: false
|
||||
filesystem_publish_endpoints: {}
|
||||
|
||||
@@ -70,6 +70,9 @@ ppa_distributor_id: ubuntu
|
||||
# Codename for short PPA url expansion
|
||||
ppa_codename: ""
|
||||
|
||||
# PPA Base URL (default: launchpad)
|
||||
# # ppa_baseurl: http://ppa.launchpad.net
|
||||
|
||||
|
||||
# Aptly Server
|
||||
###############
|
||||
@@ -80,8 +83,9 @@ serve_in_api_mode: false
|
||||
# Enable metrics for Prometheus client
|
||||
enable_metrics_endpoint: false
|
||||
|
||||
# Not implemented in this version.
|
||||
# Enable API documentation on /docs
|
||||
enable_swagger_endpoint: false
|
||||
#enable_swagger_endpoint: false
|
||||
|
||||
# OBSOLETE: use via url param ?_async=true
|
||||
async_api: false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Downloading: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease
|
||||
Downloading: http://repo.aptly.info/system-tests/ppa/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease
|
||||
gpgv: Signature made Sun Jul 28 07:57:01 2024 UTC
|
||||
gpgv: using RSA key 5BFCD481D86D5824470E469F9000B1C3A01F726C
|
||||
gpgv: Good signature from "Launchpad PPA for Anton Gladky"
|
||||
@@ -6,5 +6,5 @@ gpgv: Signature made Sun Jul 28 07:57:01 2024 UTC
|
||||
gpgv: using RSA key 02219381E9161C78A46CB2BFA5279A973B1F56C0
|
||||
gpgv: Good signature from "Launchpad sim"
|
||||
|
||||
Mirror [mirror18]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick successfully added.
|
||||
Mirror [mirror18]: http://repo.aptly.info/system-tests/ppa/gladky-anton/gnuplot/ubuntu/ maverick successfully added.
|
||||
You can run 'aptly mirror update mirror18' to download repository contents.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: mirror18
|
||||
Archive Root URL: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/
|
||||
Archive Root URL: http://repo.aptly.info/system-tests/ppa/gladky-anton/gnuplot/ubuntu/
|
||||
Distribution: maverick
|
||||
Components: main
|
||||
Architectures: amd64, armel, i386, powerpc
|
||||
|
||||
@@ -221,6 +221,7 @@ class CreateMirror18Test(BaseTest):
|
||||
"max-tries": 1,
|
||||
"ppaDistributorID": "ubuntu",
|
||||
"ppaCodename": "maverick",
|
||||
"ppaBaseURL": "http://repo.aptly.info/system-tests/ppa",
|
||||
}
|
||||
|
||||
fixtureCmds = [
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
Loading packages...
|
||||
Generating metadata files and linking package files...
|
||||
Finalizing metadata files...
|
||||
|
||||
Local repo local-repo has been successfully published.
|
||||
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
|
||||
Now you can add following line to apt sources:
|
||||
deb http://your-server/ maverick main
|
||||
deb-src http://your-server/ maverick main
|
||||
Don't forget to add your GPG key to apt with apt-key.
|
||||
|
||||
You can also use `aptly serve` to publish your repositories over HTTP quickly.
|
||||
@@ -0,0 +1,12 @@
|
||||
Origin: . maverick
|
||||
Label: . maverick
|
||||
Suite: maverick
|
||||
Codename: maverick
|
||||
Date: Fri, 13 Feb 2009 23:31:30 UTC
|
||||
Architectures: i386
|
||||
Components: main
|
||||
Description: Generated by aptly
|
||||
MD5Sum:
|
||||
SHA1:
|
||||
SHA256:
|
||||
SHA512:
|
||||
@@ -9,6 +9,10 @@ def strip_processor(output):
|
||||
return "\n".join([l for l in output.split("\n") if not l.startswith(' ') and not l.startswith('Date:')])
|
||||
|
||||
|
||||
def strip_processor_keep_date(output):
|
||||
return "\n".join([l for l in output.split("\n") if not l.startswith(' ')])
|
||||
|
||||
|
||||
class PublishRepo1Test(BaseTest):
|
||||
"""
|
||||
publish repo: default
|
||||
@@ -951,3 +955,34 @@ class PublishRepo34Test(BaseTest):
|
||||
|
||||
if 'main/dep11/README' not in pathsSeen:
|
||||
raise Exception("README file not included in release file")
|
||||
|
||||
|
||||
class PublishRepo36Test(BaseTest):
|
||||
"""
|
||||
publish repo: SOURCE_DATE_EPOCH produces byte-identical output
|
||||
"""
|
||||
fixtureCmds = [
|
||||
"aptly repo create local-repo",
|
||||
"aptly repo add local-repo ${files}",
|
||||
]
|
||||
runCmd = "aptly publish repo -skip-signing -distribution=maverick local-repo"
|
||||
gold_processor = BaseTest.expand_environ
|
||||
environmentOverride = {"SOURCE_DATE_EPOCH": "1234567890"}
|
||||
|
||||
def check(self):
|
||||
super(PublishRepo36Test, self).check()
|
||||
|
||||
# verify Release file includes the expected date from SOURCE_DATE_EPOCH
|
||||
self.check_file_contents(
|
||||
'public/dists/maverick/Release', 'release', match_prepare=strip_processor_keep_date)
|
||||
|
||||
# save Release file from first publish
|
||||
first_release = self.read_file('public/dists/maverick/Release')
|
||||
|
||||
# drop and republish with same SOURCE_DATE_EPOCH
|
||||
self.run_cmd("aptly publish drop maverick")
|
||||
self.run_cmd("aptly publish repo -skip-signing -distribution=maverick local-repo")
|
||||
|
||||
# verify byte-identical output
|
||||
second_release = self.read_file('public/dists/maverick/Release')
|
||||
self.check_equal(first_release, second_release)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
gpg: Signature made Mon Jan 26 10:18:32 2026 UTC
|
||||
gpg: using DSA key C5ACD2179B5231DFE842EE6121DBB89C16DB3E6D
|
||||
gpg: checking the trustdb
|
||||
gpg: no ultimately trusted keys found
|
||||
gpg: Good signature from "Aptly Tester (don't use it) <test@aptly.info>" [unknown]
|
||||
gpg: WARNING: This key is not certified with a trusted signature!
|
||||
gpg: There is no indication that the signature belongs to the owner.
|
||||
Primary key fingerprint: C5AC D217 9B52 31DF E842 EE61 21DB B89C 16DB 3E6D
|
||||
gpg: Signature made Mon Jan 26 10:18:32 2026 UTC
|
||||
gpg: using EDDSA key AEE16DF018354F67FE5F5C72BBF4E19434E91E4E
|
||||
gpg: Good signature from "Aptly Secondary Signing Key <aptly@example.com>" [unknown]
|
||||
gpg: WARNING: This key is not certified with a trusted signature!
|
||||
gpg: There is no indication that the signature belongs to the owner.
|
||||
Primary key fingerprint: AEE1 6DF0 1835 4F67 FE5F 5C72 BBF4 E194 34E9 1E4E
|
||||
@@ -1,17 +0,0 @@
|
||||
from api_lib import APITest
|
||||
|
||||
|
||||
class TaskAPITestSwaggerDocs(APITest):
|
||||
"""
|
||||
GET /docs
|
||||
"""
|
||||
|
||||
def check(self):
|
||||
resp = self.get("/docs/doc.json")
|
||||
self.check_equal(resp.status_code, 200)
|
||||
|
||||
resp = self.get("/docs/", allow_redirects=False)
|
||||
self.check_equal(resp.status_code, 301)
|
||||
|
||||
resp = self.get("/docs/index.html")
|
||||
self.check_equal(resp.status_code, 200)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user