mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-07-25 13:37:51 +00:00
Merge tag 'upstream/1.6.0+ds1_alpha1' into debian/master
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
.go/
|
||||||
|
.git/
|
||||||
|
obj-x86_64-linux-gnu/
|
||||||
|
unit.out
|
||||||
|
aptly.test
|
||||||
|
build/
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 200
|
max-line-length = 240
|
||||||
ignore = E126,E241,E741,W504
|
ignore = E126,E241,E741,W504
|
||||||
include =
|
include =
|
||||||
system
|
system
|
||||||
exclude =
|
exclude =
|
||||||
system/env
|
system/env
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
github: aptly-dev
|
||||||
+224
-89
@@ -1,5 +1,3 @@
|
|||||||
# Based on https://github.com/aptly-dev/aptly/blob/master/.travis.yml
|
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -13,32 +11,17 @@ on:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
# see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
|
# see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
|
||||||
shell: bash --noprofile --norc -eo pipefail -x {0}
|
shell: bash --noprofile --norc -eo pipefail {0}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
name: test
|
name: "Test (Ubuntu 22.04)"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
continue-on-error: ${{ matrix.allow_failure }}
|
continue-on-error: false
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
go: [1.16, 1.17, 1.18]
|
|
||||||
run_long_tests: [no]
|
|
||||||
allow_failure: [false]
|
|
||||||
include:
|
|
||||||
# Disable this for now as it's not clear how to select the latest master branch
|
|
||||||
# version of go using actions/setup-go@v2.
|
|
||||||
# - go: master
|
|
||||||
# run_long_tests: no
|
|
||||||
# allow_failure: true
|
|
||||||
- go: 1.17
|
|
||||||
run_long_tests: yes
|
|
||||||
allow_failure: false
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NO_FTP_ACCESS: yes
|
NO_FTP_ACCESS: yes
|
||||||
@@ -47,101 +30,253 @@ jobs:
|
|||||||
GOPROXY: "https://proxy.golang.org"
|
GOPROXY: "https://proxy.golang.org"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: "Install packages"
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go }}
|
|
||||||
|
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v2
|
|
||||||
with:
|
|
||||||
version: v1.45.0
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
|
|
||||||
- name: Install O/S packages
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y graphviz gnupg1 gnupg2 gpgv1 gpgv2 git gcc make
|
sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8
|
||||||
|
|
||||||
- name: Install Python packages
|
- name: "Checkout repository"
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# fetch the whole repo for `git describe` to work
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: "Run flake8"
|
||||||
run: |
|
run: |
|
||||||
pip install six packaging appdirs virtualenv
|
make flake8
|
||||||
pip install -U pip setuptools
|
|
||||||
pip install -r system/requirements.txt
|
|
||||||
|
|
||||||
- name: Install Azurite
|
- name: "Read go version from go.mod"
|
||||||
|
run: |
|
||||||
|
gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod)
|
||||||
|
echo "Go Version: $gover"
|
||||||
|
echo "GOVER=$gover" >> $GITHUB_OUTPUT
|
||||||
|
id: goversion
|
||||||
|
|
||||||
|
- name: "Setup Go"
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||||
|
|
||||||
|
- name: "Install Azurite"
|
||||||
id: azuright
|
id: azuright
|
||||||
uses: potatoqualitee/azuright@v1.1
|
uses: potatoqualitee/azuright@v1.1
|
||||||
with:
|
with:
|
||||||
directory: ${{ runner.temp }}
|
directory: ${{ runner.temp }}
|
||||||
|
|
||||||
- name: Get aptly version
|
- name: "Run Unit Tests"
|
||||||
run: |
|
|
||||||
make version
|
|
||||||
|
|
||||||
- name: Make
|
|
||||||
env:
|
env:
|
||||||
RUN_LONG_TESTS: ${{ matrix.run_long_tests }}
|
RUN_LONG_TESTS: 'yes'
|
||||||
AZURE_STORAGE_ENDPOINT: "127.0.0.1:10000"
|
AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1"
|
||||||
AZURE_STORAGE_ACCOUNT: "devstoreaccount1"
|
AZURE_STORAGE_ACCOUNT: "devstoreaccount1"
|
||||||
AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
|
AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
run: |
|
run: |
|
||||||
make
|
sudo mkdir -p /srv ; sudo chown runner /srv
|
||||||
|
COVERAGE_DIR=${{ runner.temp }} make test
|
||||||
|
|
||||||
- name: Upload code coverage
|
- name: "Run Benchmark"
|
||||||
if: matrix.run_long_tests
|
run: |
|
||||||
|
COVERAGE_DIR=${{ runner.temp }} make bench
|
||||||
|
|
||||||
|
- name: "Run System Tests"
|
||||||
|
env:
|
||||||
|
RUN_LONG_TESTS: 'yes'
|
||||||
|
AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1"
|
||||||
|
AZURE_STORAGE_ACCOUNT: "devstoreaccount1"
|
||||||
|
AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
run: |
|
||||||
|
sudo mkdir -p /srv ; sudo chown runner /srv
|
||||||
|
COVERAGE_DIR=${{ runner.temp }} make system-test
|
||||||
|
|
||||||
|
- name: "Merge code coverage"
|
||||||
|
run: |
|
||||||
|
go install github.com/wadey/gocovmerge@latest
|
||||||
|
~/go/bin/gocovmerge unit.out ${{ runner.temp }}/*.out > coverage.txt
|
||||||
|
|
||||||
|
- name: "Upload code coverage"
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: coverage.txt
|
files: coverage.txt
|
||||||
|
|
||||||
release:
|
ci-debian-build:
|
||||||
name: release
|
name: "Build"
|
||||||
needs: build
|
needs: test
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
name: ["Debian 13/testing", "Debian 12/bookworm", "Debian 11/bullseye", "Debian 10/buster", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
||||||
|
arch: ["amd64", "i386" , "arm64" , "armhf"]
|
||||||
|
include:
|
||||||
|
- name: "Debian 13/testing"
|
||||||
|
suite: trixie
|
||||||
|
image: debian:trixie-slim
|
||||||
|
- name: "Debian 12/bookworm"
|
||||||
|
suite: bookworm
|
||||||
|
image: debian:bookworm-slim
|
||||||
|
- name: "Debian 11/bullseye"
|
||||||
|
suite: bullseye
|
||||||
|
image: debian:bullseye-slim
|
||||||
|
- name: "Debian 10/buster"
|
||||||
|
suite: buster
|
||||||
|
image: debian:buster-slim
|
||||||
|
- name: "Ubuntu 24.04"
|
||||||
|
suite: noble
|
||||||
|
image: ubuntu:24.04
|
||||||
|
- name: "Ubuntu 22.04"
|
||||||
|
suite: jammy
|
||||||
|
image: ubuntu:22.04
|
||||||
|
- name: "Ubuntu 20.04"
|
||||||
|
suite: focal
|
||||||
|
image: ubuntu:20.04
|
||||||
|
container:
|
||||||
|
image: ${{ matrix.image }}
|
||||||
|
env:
|
||||||
|
APT_LISTCHANGES_FRONTEND: none
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: "Install packages"
|
||||||
uses: actions/checkout@v3
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang binutils-i686-linux-gnu binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf jq
|
||||||
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
- name: "Checkout repository"
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# fetch the whole repot for `git describe` to
|
# fetch the whole repo for `git describe` to work
|
||||||
# work and get the nightly verion
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Go
|
- name: "Read go version from go.mod"
|
||||||
uses: actions/setup-go@v2
|
run: |
|
||||||
|
gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod)
|
||||||
|
echo "Go Version: $gover"
|
||||||
|
echo "GOVER=$gover" >> $GITHUB_OUTPUT
|
||||||
|
id: goversion
|
||||||
|
|
||||||
- name: Make Release
|
- name: "Setup Go"
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||||
|
|
||||||
|
- name: "Ensure CI build"
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
run: |
|
||||||
|
echo "FORCE_CI=true" >> $GITHUB_OUTPUT
|
||||||
|
id: force_ci
|
||||||
|
|
||||||
|
- name: "Build Debian packages"
|
||||||
|
env:
|
||||||
|
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
|
||||||
|
run: |
|
||||||
|
make dpkg DEBARCH=${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: "Check aptly credentials"
|
||||||
|
env:
|
||||||
|
APTLY_USER: ${{ secrets.APTLY_USER }}
|
||||||
|
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
found=no
|
||||||
|
if [ -n "$APTLY_USER" ] && [ -n "$APTLY_PASSWORD" ]; then
|
||||||
|
found=yes
|
||||||
|
fi
|
||||||
|
echo "Aptly credentials available: $found"
|
||||||
|
echo "FOUND=$found" >> $GITHUB_OUTPUT
|
||||||
|
id: aptlycreds
|
||||||
|
|
||||||
|
- name: "Publish CI release to aptly"
|
||||||
|
if: github.ref == 'refs/heads/master' && steps.aptlycreds.outputs.FOUND == 'yes'
|
||||||
|
env:
|
||||||
|
APTLY_USER: ${{ secrets.APTLY_USER }}
|
||||||
|
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
.github/workflows/scripts/upload-artifacts.sh ci ${{ matrix.suite }}
|
||||||
|
|
||||||
|
- name: "Publish release to aptly"
|
||||||
|
if: startsWith(github.event.ref, 'refs/tags') && steps.aptlycreds.outputs.FOUND == 'yes'
|
||||||
|
env:
|
||||||
|
APTLY_USER: ${{ secrets.APTLY_USER }}
|
||||||
|
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
.github/workflows/scripts/upload-artifacts.sh release ${{ matrix.suite }}
|
||||||
|
|
||||||
|
ci-binary-build:
|
||||||
|
name: "Build"
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
goos: [linux, freebsd, darwin]
|
||||||
|
goarch: ["386", "amd64", "arm", "arm64"]
|
||||||
|
exclude:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: 386
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm
|
||||||
|
steps:
|
||||||
|
- name: "Checkout repository"
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# fetch the whole repo for `git describe` to work
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: "Read go version from go.mod"
|
||||||
|
run: |
|
||||||
|
echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT
|
||||||
|
id: goversion
|
||||||
|
|
||||||
|
- name: "Setup Go"
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||||
|
|
||||||
|
- name: "Ensure CI build"
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
run: |
|
||||||
|
echo "FORCE_CI=true" >> $GITHUB_OUTPUT
|
||||||
|
id: force_ci
|
||||||
|
|
||||||
|
- name: "Get aptly version"
|
||||||
|
env:
|
||||||
|
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
|
||||||
|
run: |
|
||||||
|
aptlyver=$(make -s version)
|
||||||
|
echo "Aptly Version: $aptlyver"
|
||||||
|
echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT
|
||||||
|
id: releaseversion
|
||||||
|
|
||||||
|
- name: "Build aptly ${{ matrix.goos }}/${{ matrix.goarch }}"
|
||||||
env:
|
env:
|
||||||
GOBIN: /usr/local/bin
|
GOBIN: /usr/local/bin
|
||||||
|
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
|
||||||
run: |
|
run: |
|
||||||
make release
|
make binaries GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }}
|
||||||
|
|
||||||
- name: Publish nightly release to aptly
|
- name: "Upload Artifacts"
|
||||||
if: github.ref == 'refs/heads/master'
|
uses: actions/upload-artifact@v4
|
||||||
env:
|
|
||||||
APTLY_USER: ${{ secrets.APTLY_USER }}
|
|
||||||
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
./upload-artifacts.sh nightly
|
|
||||||
|
|
||||||
- name: Publish release to aptly
|
|
||||||
if: startsWith(github.event.ref, 'refs/tags')
|
if: startsWith(github.event.ref, 'refs/tags')
|
||||||
env:
|
|
||||||
APTLY_USER: ${{ secrets.APTLY_USER }}
|
|
||||||
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
./upload-artifacts.sh release
|
|
||||||
|
|
||||||
- name: Upload artifacts to GitHub Release
|
|
||||||
if: startsWith(github.event.ref, 'refs/tags')
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
with:
|
||||||
body: Release ${{ github.ref }} generated by the CI.
|
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||||
files: build/*
|
path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
|
||||||
|
compression-level: 0 # no compression
|
||||||
|
|
||||||
|
gh-release:
|
||||||
|
name: "Github Release"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: false
|
||||||
|
needs: ci-binary-build
|
||||||
|
if: startsWith(github.event.ref, 'refs/tags')
|
||||||
|
steps:
|
||||||
|
- name: "Download Artifacts"
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: out/
|
||||||
|
|
||||||
|
- name: "Release"
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: "out/**/aptly_*.zip"
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
name: golangci-lint
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
# Optional: allow read access to pull request. Use with `only-new-issues` option.
|
||||||
|
# pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: "Read go version from go.mod"
|
||||||
|
run: |
|
||||||
|
echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT
|
||||||
|
id: goversion
|
||||||
|
|
||||||
|
- name: "Setup Go"
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||||
|
|
||||||
|
- name: Create VERSION file
|
||||||
|
run: |
|
||||||
|
make -s version | tr -d '\n' > VERSION
|
||||||
|
shell: sh
|
||||||
|
|
||||||
|
- name: Install and initialize swagger
|
||||||
|
run: |
|
||||||
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
swag init -q --markdownFiles docs
|
||||||
|
shell: sh
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v4
|
||||||
|
with:
|
||||||
|
# Require: The version of golangci-lint to use.
|
||||||
|
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
|
||||||
|
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
|
||||||
|
version: v1.54.1
|
||||||
|
|
||||||
|
# Optional: working directory, useful for monorepos
|
||||||
|
# working-directory: somedir
|
||||||
|
|
||||||
|
# Optional: golangci-lint command line arguments.
|
||||||
|
#
|
||||||
|
# Note: By default, the `.golangci.yml` file should be at the root of the repository.
|
||||||
|
# The location of the configuration file can be changed by using `--config=`
|
||||||
|
# args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0
|
||||||
|
|
||||||
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
|
# only-new-issues: true
|
||||||
|
|
||||||
|
# Optional: if set to true, then all caching functionality will be completely disabled,
|
||||||
|
# takes precedence over all other caching options.
|
||||||
|
# skip-cache: true
|
||||||
|
|
||||||
|
# Optional: if set to true, then the action won't cache or restore ~/go/pkg.
|
||||||
|
# skip-pkg-cache: true
|
||||||
|
|
||||||
|
# Optional: if set to true, then the action won't cache or restore ~/.cache/go-build.
|
||||||
|
# skip-build-cache: true
|
||||||
|
|
||||||
|
# Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'.
|
||||||
|
# install-mode: "goinstall"
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
builds=build/
|
||||||
|
packages=${builds}*.deb
|
||||||
|
folder=`mktemp -u tmp.XXXXXXXXXXXXXXX`
|
||||||
|
aptly_user="$APTLY_USER"
|
||||||
|
aptly_password="$APTLY_PASSWORD"
|
||||||
|
aptly_api="https://aptly-ops.aptly.info"
|
||||||
|
version=`make version`
|
||||||
|
|
||||||
|
action=$1
|
||||||
|
dist=$2
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 ci buster|bullseye|bookworm|focal|jammy|noble" >&2
|
||||||
|
echo " $0 release" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# repos and publish must be created beforehand:
|
||||||
|
#!/bin/sh
|
||||||
|
#for dist in buster bullseye bookworm focal jammy noble
|
||||||
|
#do
|
||||||
|
# for build in ci release
|
||||||
|
# do
|
||||||
|
# echo
|
||||||
|
# echo "# Creating and publishing $build/$dist"
|
||||||
|
# aptly repo create -distribution=$dist -component=main aptly-$build-$dist
|
||||||
|
# aptly publish repo -multi-dist -architectures="amd64,i386,arm64,armhf" -acquire-by-hash -component=main \
|
||||||
|
# -distribution=$dist -batch -keyring=aptly.pub \
|
||||||
|
# aptly-$build-$dist \
|
||||||
|
# s3:repo.aptly.info:$build
|
||||||
|
# done
|
||||||
|
#done
|
||||||
|
|
||||||
|
if [ -z "$action" ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "action" = "ci" ] && [ -z "$dist" ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$aptly_user" ] || [ -z "$aptly_password" ]; then
|
||||||
|
usage
|
||||||
|
echo Error: please set APTLY_USER and APTLY_PASSWORD
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Publishing version '$version' to $action for $dist...\n"
|
||||||
|
|
||||||
|
upload()
|
||||||
|
{
|
||||||
|
echo "\nUploading files:"
|
||||||
|
for file in $packages; do
|
||||||
|
echo " - $file"
|
||||||
|
jsonret=`curl -fsS -X POST -F "file=@$file" -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder`
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "\nCleanup..."
|
||||||
|
jsonret=`curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder`
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
update_publish() {
|
||||||
|
_publish=$1
|
||||||
|
_dist=$2
|
||||||
|
jsonret=`curl -fsS -X PUT -H 'Content-Type: application/json' --data \
|
||||||
|
'{"AcquireByHash": true, "MultiDist": true,
|
||||||
|
"Signing": {"Batch": true, "Keyring": "aptly.repo/aptly.pub", "secretKeyring": "aptly.repo/aptly.sec", "PassphraseFile": "aptly.repo/passphrase"}}' \
|
||||||
|
-u $aptly_user:$aptly_password ${aptly_api}/api/publish/$_publish/$_dist?_async=true`
|
||||||
|
_task_id=`echo $jsonret | jq .ID`
|
||||||
|
_success=0
|
||||||
|
for t in `seq 180`
|
||||||
|
do
|
||||||
|
jsonret=`curl -fsS -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_task_id`
|
||||||
|
_state=`echo $jsonret | jq .State`
|
||||||
|
if [ "$_state" = "2" ]; then
|
||||||
|
_success=1
|
||||||
|
curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_task_id
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$_state" = "3" ]; then
|
||||||
|
echo Error: publish failed
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ "$_success" -ne 1 ]; then
|
||||||
|
echo "Error: publish failed (timeout)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$action" = "ci" ]; then
|
||||||
|
if echo "$version" | grep -vq "+"; then
|
||||||
|
# skip ci when on release tag
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
aptly_repository=aptly-ci-$dist
|
||||||
|
aptly_published=s3:repo.aptly.info:ci
|
||||||
|
|
||||||
|
elif [ "$action" = "release" ]; then
|
||||||
|
aptly_repository=aptly-release-$dist
|
||||||
|
aptly_published=s3:repo.aptly.info:release
|
||||||
|
fi
|
||||||
|
|
||||||
|
upload
|
||||||
|
|
||||||
|
echo "\nAdding packages to $aptly_repository ..."
|
||||||
|
jsonret=`curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder`
|
||||||
|
|
||||||
|
echo "\nUpdating published repo $aptly_published ..."
|
||||||
|
update_publish $aptly_published $dist
|
||||||
|
|
||||||
|
# if [ "$action" = "OBSOLETErelease" ]; then
|
||||||
|
# aptly_repository=aptly-release
|
||||||
|
# aptly_snapshot=aptly-$version
|
||||||
|
# aptly_published=s3:repo.aptly.info:./squeeze
|
||||||
|
#
|
||||||
|
# echo "\nAdding packages to $aptly_repository..."
|
||||||
|
# curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder
|
||||||
|
# echo
|
||||||
|
#
|
||||||
|
# echo "\nCreating snapshot $aptly_snapshot from repo $aptly_repository..."
|
||||||
|
# curl -fsS -X POST -u $aptly_user:$aptly_password -H 'Content-Type: application/json' --data \
|
||||||
|
# "{\"Name\":\"$aptly_snapshot\"}" ${aptly_api}/api/repos/$aptly_repository/snapshots
|
||||||
|
# echo
|
||||||
|
#
|
||||||
|
# echo "\nSwitching published repo $aptly_published to use snapshot $aptly_snapshot..."
|
||||||
|
# curl -fsS -X PUT -H 'Content-Type: application/json' --data \
|
||||||
|
# "{\"AcquireByHash\": true, \"Snapshots\": [{\"Component\": \"main\", \"Name\": \"$aptly_snapshot\"}],
|
||||||
|
# \"Signing\": {\"Batch\": true, \"Keyring\": \"aptly.repo/aptly.pub\",
|
||||||
|
# \"secretKeyring\": \"aptly.repo/aptly.sec\", \"PassphraseFile\": \"aptly.repo/passphrase\"}}" \
|
||||||
|
# -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$aptly_published
|
||||||
|
# echo
|
||||||
|
# fi
|
||||||
|
|
||||||
+31
-5
@@ -2,11 +2,14 @@
|
|||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
|
unit.out
|
||||||
|
|
||||||
# Folders
|
# Folders
|
||||||
_obj
|
_obj
|
||||||
_test
|
_test
|
||||||
|
|
||||||
|
tmp/
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
# Architecture specific extensions/prefixes
|
||||||
*.[568vq]
|
*.[568vq]
|
||||||
[568vq].out
|
[568vq].out
|
||||||
@@ -32,13 +35,36 @@ root/
|
|||||||
man/aptly.1.html
|
man/aptly.1.html
|
||||||
man/aptly.1.ronn
|
man/aptly.1.ronn
|
||||||
|
|
||||||
.goxc.local.json
|
|
||||||
|
|
||||||
system/env/
|
system/env/
|
||||||
|
|
||||||
# created by make build for release artifacts
|
# created by make build for release artifacts
|
||||||
|
VERSION
|
||||||
|
aptly.test
|
||||||
|
|
||||||
build/
|
build/
|
||||||
|
|
||||||
pgp/keyrings/aptly2*.gpg
|
system/files/aptly2.gpg~
|
||||||
pgp/keyrings/aptly2*.gpg~
|
system/files/aptly2_passphrase.gpg~
|
||||||
pgp/keyrings/.#*
|
|
||||||
|
*.creds
|
||||||
|
|
||||||
|
.go/
|
||||||
|
obj-x86_64-linux-gnu/
|
||||||
|
|
||||||
|
# debian
|
||||||
|
debian/.debhelper/
|
||||||
|
debian/aptly.debhelper.log
|
||||||
|
debian/aptly.postrm.debhelper
|
||||||
|
debian/aptly.substvars
|
||||||
|
debian/aptly/
|
||||||
|
debian/debhelper-build-stamp
|
||||||
|
debian/files
|
||||||
|
debian/aptly-api/
|
||||||
|
debian/aptly-api.debhelper.log
|
||||||
|
debian/aptly-api.postrm.debhelper
|
||||||
|
debian/aptly-api.substvars
|
||||||
|
debian/aptly-dbg.debhelper.log
|
||||||
|
debian/aptly-dbg.substvars
|
||||||
|
debian/aptly-dbg/
|
||||||
|
|
||||||
|
docs/
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ run:
|
|||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- deadcode
|
|
||||||
- goconst
|
- goconst
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
@@ -14,6 +13,4 @@ linters:
|
|||||||
- misspell
|
- misspell
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
|
||||||
- varcheck
|
|
||||||
- vetshadow
|
- vetshadow
|
||||||
|
|||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"AppName": "aptly",
|
|
||||||
"ArtifactsDest": "xc-out/",
|
|
||||||
"TasksExclude": [
|
|
||||||
"rmbin",
|
|
||||||
"go-test",
|
|
||||||
"go-vet"
|
|
||||||
],
|
|
||||||
"TasksAppend": [
|
|
||||||
"bintray"
|
|
||||||
],
|
|
||||||
"TaskSettings": {
|
|
||||||
"debs": {
|
|
||||||
"metadata": {
|
|
||||||
"maintainer": "Andrey Smirnov",
|
|
||||||
"maintainer-email": "me@smira.ru",
|
|
||||||
"description": "Debian repository management tool"
|
|
||||||
},
|
|
||||||
"metadata-deb": {
|
|
||||||
"License": "MIT",
|
|
||||||
"Homepage": "https://www.aptly.info/",
|
|
||||||
"Depends": "bzip2, xz-utils, gnupg, gpgv",
|
|
||||||
"Suggests": "graphviz"
|
|
||||||
},
|
|
||||||
"other-mapped-files": {
|
|
||||||
"/": "root/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bintray": {
|
|
||||||
"repository": "aptly",
|
|
||||||
"subject": "smira",
|
|
||||||
"package": "aptly",
|
|
||||||
"downloadspage": "bintray.md"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ResourcesInclude": "README.rst,LICENSE,AUTHORS,man/aptly.1",
|
|
||||||
"BuildConstraints": "linux,386 linux,amd64 darwin,amd64 freebsd,386 freebsd,amd64",
|
|
||||||
"MainDirsExclude": "_man,vendor",
|
|
||||||
"BuildSettings": {
|
|
||||||
"LdFlagsXVars": {
|
|
||||||
"Version": "main.Version"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ConfigVersion": "0.9"
|
|
||||||
}
|
|
||||||
-106
@@ -1,106 +0,0 @@
|
|||||||
dist: xenial
|
|
||||||
sudo: required
|
|
||||||
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go_import_path: github.com/aptly-dev/aptly
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- python-virtualenv
|
|
||||||
- graphviz
|
|
||||||
- gnupg2
|
|
||||||
- gpgv2
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- secure: "EcCzJsqQ3HnIkprBPS1YHErsETcb7KQFBYEzVDE7RYDApWeapLq+r/twMtWMd/fkGeLzr3kWSg7nhSadeHMLYeMl9j+U7ncC5CWG5NMBOj/jowlb9cMCCDlmzMoZLAgR6jm1cJyrWCLsWVlv+D0ZiB0fx4xaBZP/gIr9g6nEwC8="
|
|
||||||
- secure: "OxiVNmre2JzUszwPNNilKDgIqtfX2gnRSsVz6nuySB1uO2yQsOQmKWJ9cVYgH2IB5H8eWXKOhexcSE28kz6TPLRuEcU9fnqKY3uEkdwm7rJfz9lf+7C4bJEUdA1OIzJppjnWUiXxD7CEPL1DlnMZM24eDQYqa/4WKACAgkK53gE="
|
|
||||||
- NO_FTP_ACCESS: "yes"
|
|
||||||
- BOTO_CONFIG: /dev/null
|
|
||||||
- GO111MODULE: "on"
|
|
||||||
- GOPROXY: https://proxy.golang.org
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: master
|
|
||||||
env: RUN_LONG_TESTS=no
|
|
||||||
fast_finish: true
|
|
||||||
include:
|
|
||||||
- go: 1.11.x
|
|
||||||
env: RUN_LONG_TESTS=no
|
|
||||||
- go: 1.12.x
|
|
||||||
env: RUN_LONG_TESTS=no
|
|
||||||
- go: 1.13.x
|
|
||||||
env: RUN_LONG_TESTS=no
|
|
||||||
- go: 1.14.x
|
|
||||||
env: RUN_LONG_TESTS=yes
|
|
||||||
- go: 1.15.x
|
|
||||||
env:
|
|
||||||
- RUN_LONG_TESTS=yes
|
|
||||||
- DEPLOY_BINARIES=yes
|
|
||||||
- APTLY_USER=aptly
|
|
||||||
- secure: "ejVss+Ansvk9f237iXVd87KA8N/SkfJkEdr/KCw9WRkVw3M9WyYtFnqpakIUPFT8RsSc7MW+RU4OM90DsbE9dbDIL0oW+t6QH/IfGjNG2HjDiGEWN/tMLeAQTtzPaVqlItJBo0ILMF2K6NrgkYBYU+tZ8gk5w7CuARvAk82d00o="
|
|
||||||
- go: master
|
|
||||||
env: RUN_LONG_TESTS=no
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- virtualenv system/env
|
|
||||||
- . system/env/bin/activate
|
|
||||||
- pip install six packaging appdirs
|
|
||||||
- pip install -U pip setuptools
|
|
||||||
- pip install -r system/requirements.txt
|
|
||||||
- make version
|
|
||||||
|
|
||||||
install:
|
|
||||||
- make prepare
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- "https://webhooks.gitter.im/e/c691da114a41eed6ec45"
|
|
||||||
on_success: change # options: [always|never|change] default: always
|
|
||||||
on_failure: always # options: [always|never|change] default: always
|
|
||||||
on_start: false # default: false
|
|
||||||
|
|
||||||
before_deploy:
|
|
||||||
- make release
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: releases
|
|
||||||
api_key:
|
|
||||||
secure: XHxYAFBzzgOZyK6JXQpEp0kGrZPmd02esEJjwJXZpWT68kRzCCrBXg+x3vIcgRtl82oQbflv/ThNlGT80iqSmd+Itsa5lUJoJnRxbP8qSykfCXmkrgsHIxbGxWIL+JHAWmwQdkV91kDS04nmjl9MDptLId0tuleWwcMH6h1hgMg=
|
|
||||||
file_glob: true
|
|
||||||
file: build/*
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
condition: "$DEPLOY_BINARIES = yes"
|
|
||||||
- provider: s3
|
|
||||||
access_key_id:
|
|
||||||
secure: "I2etn22HHsQjJNhr6zdM/P4VLCYwEA/6HEf2eGvwey93oLeog+KnDCUI7lwGAHYuwzyDGQbZZ6YdoNc3b0kxaRWT0W+ke78TAdJhTZ+xbqGfEWv1er0zklJLOsimYF097rDJw8g3Oh/Gjwt5TTp0GJ5l3IhJ6zepNsKCMuwQpJM="
|
|
||||||
secret_access_key:
|
|
||||||
secure: "inRWX7FuyhkhKzGknSd2/mjZaNFZm/zHMejM99OF6PiGLNtyt/esdA0ToYL8B8Icl0/SISlLlEr/DDa4OGENKueFVeHrKH7OK0jVbWp9Yvw4hCXSlw9VmlkHDMQrC4gybS2Hf7el8N4AFVqyeUE7LqiP3WruHRdbE9XgOnTkLkg="
|
|
||||||
bucket: aptly-nightly
|
|
||||||
skip_cleanup: true
|
|
||||||
acl: public-read
|
|
||||||
local_dir: build
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
condition: "$DEPLOY_BINARIES = yes"
|
|
||||||
- provider: script
|
|
||||||
script: bash upload-artifacts.sh nightly
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
condition: "$DEPLOY_BINARIES = yes"
|
|
||||||
- provider: script
|
|
||||||
script: bash upload-artifacts.sh release
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
condition: "$DEPLOY_BINARIES = yes"
|
|
||||||
@@ -49,3 +49,17 @@ List of contributors, in chronological order:
|
|||||||
* Samuel Mutel (https://github.com/smutel)
|
* Samuel Mutel (https://github.com/smutel)
|
||||||
* Russell Greene (https://github.com/russelltg)
|
* Russell Greene (https://github.com/russelltg)
|
||||||
* Wade Simmons (https://github.com/wadey)
|
* Wade Simmons (https://github.com/wadey)
|
||||||
|
* Steven Stone (https://github.com/smstone)
|
||||||
|
* Josh Bayfield (https://github.com/jbayfield)
|
||||||
|
* Boxjan (https://github.com/boxjan)
|
||||||
|
* Mauro Regli (https://github.com/reglim)
|
||||||
|
* Alexander Zubarev (https://github.com/strike)
|
||||||
|
* Nicolas Dostert (https://github.com/acdn-ndostert)
|
||||||
|
* Ryan Gonzalez (https://github.com/refi64)
|
||||||
|
* Paul Cacheux (https://github.com/paulcacheux)
|
||||||
|
* Nic Waller (https://github.com/sf-nwaller)
|
||||||
|
* iofq (https://github.com/iofq)
|
||||||
|
* Noa Resare (https://github.com/nresare)
|
||||||
|
* Ramón N.Rodriguez (https://github.com/runitonmetal)
|
||||||
|
* Golf Hu (https://github.com/hudeng-go)
|
||||||
|
* Cookie Fei (https://github.com/wuhuang26)
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting the project team at [team@aptly.info](mailto:team@aptly.info). All
|
reported by contacting the project team on [Aptly Discussions](https://github.com/aptly-dev/aptly/discussions). All
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
|||||||
+96
-45
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||||
|
|
||||||
The following is a set of guidelines for contributing to [aptly](https://github.com/smira/aplty) and related repositories, which are hosted in the [aptly-dev Organization](https://github.com/aptly-dev) on GitHub.
|
The following is a set of guidelines for contributing to [aptly](https://github.com/aptly-dev/aplty) and related repositories, which are hosted in the [aptly-dev Organization](https://github.com/aptly-dev) on GitHub.
|
||||||
These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||||
|
|
||||||
## What should I know before I get started?
|
## What should I know before I get started?
|
||||||
@@ -11,7 +11,7 @@ These are just guidelines, not rules. Use your best judgment, and feel free to p
|
|||||||
|
|
||||||
This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
|
This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
|
||||||
By participating, you are expected to uphold this code.
|
By participating, you are expected to uphold this code.
|
||||||
Please report unacceptable behavior to [team@aptly.info](mailto:team@aptly.info).
|
Please report unacceptable behavior on [https://github.com/aptly-dev/aptly/discussions](https://github.com/aptly-dev/aptly/discussions)
|
||||||
|
|
||||||
### List of Repositories
|
### List of Repositories
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ If you want to update website, please follow steps below:
|
|||||||
We're always looking for new contributions to [FAQ](https://www.aptly.info/doc/faq/), [tutorials](https://www.aptly.info/tutorial/),
|
We're always looking for new contributions to [FAQ](https://www.aptly.info/doc/faq/), [tutorials](https://www.aptly.info/tutorial/),
|
||||||
general fixes, clarifications, misspellings, grammar mistakes!
|
general fixes, clarifications, misspellings, grammar mistakes!
|
||||||
|
|
||||||
### Your First Code Contribution
|
### Code Contribution
|
||||||
|
|
||||||
Please follow [next section](#development-setup) on development process. When change is ready, please submit PR
|
Please follow [next section](#development-setup) on development process. When change is ready, please submit PR
|
||||||
following [PR template](.github/PULL_REQUEST_TEMPLATE.md).
|
following [PR template](.github/PULL_REQUEST_TEMPLATE.md).
|
||||||
@@ -68,20 +68,6 @@ following [PR template](.github/PULL_REQUEST_TEMPLATE.md).
|
|||||||
Make sure that purpose of your change is clear, all the tests and checks pass, and all new code is covered with tests
|
Make sure that purpose of your change is clear, all the tests and checks pass, and all new code is covered with tests
|
||||||
if that is possible.
|
if that is possible.
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
This section describes local setup to start contributing to aptly source.
|
|
||||||
|
|
||||||
### Go & Python
|
|
||||||
|
|
||||||
You would need `Go` (latest version is recommended) and `Python` 2.7.x (3.x is not supported yet).
|
|
||||||
|
|
||||||
If you're new to Go, follow [getting started guide](https://golang.org/doc/install) to install it and perform
|
|
||||||
initial setup. With Go 1.8+, default `$GOPATH` is `$HOME/go`, so rest of this document assumes that.
|
|
||||||
|
|
||||||
Usually `$GOPATH/bin` is appended to your `$PATH` to make it easier to run built binaries, but you might choose
|
|
||||||
to prepend it or to skip this test if you're security conscious.
|
|
||||||
|
|
||||||
### Forking and Cloning
|
### Forking and Cloning
|
||||||
|
|
||||||
As aptly is using Go modules, aptly repository could be cloned to any location on the file system:
|
As aptly is using Go modules, aptly repository could be cloned to any location on the file system:
|
||||||
@@ -98,7 +84,94 @@ to specify your remote name when pushing branches:
|
|||||||
|
|
||||||
git push <user> <your-branch>
|
git push <user> <your-branch>
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
Working on aptly code can be done locally on the development machine, or for convenience by using docker. The next sections describe the setup process.
|
||||||
|
|
||||||
|
### Docker Development Setup
|
||||||
|
|
||||||
|
This section describes the docker setup to start contributing to aptly.
|
||||||
|
|
||||||
|
#### Dependencies
|
||||||
|
|
||||||
|
Install the following on your development machine:
|
||||||
|
- docker
|
||||||
|
- make
|
||||||
|
- git
|
||||||
|
|
||||||
|
|
||||||
|
#### Create docker container
|
||||||
|
|
||||||
|
To build the development docker image, run:
|
||||||
|
```
|
||||||
|
make docker-image
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Build aptly
|
||||||
|
|
||||||
|
To build the aptly in the development docker container, run:
|
||||||
|
```
|
||||||
|
make docker-build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Running aptly commands
|
||||||
|
|
||||||
|
To run aptly commands in the development docker container, run:
|
||||||
|
```
|
||||||
|
make docker-aptly
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
$ make docker-aptly
|
||||||
|
bash: cannot set terminal process group (16): Inappropriate ioctl for device
|
||||||
|
bash: no job control in this shell
|
||||||
|
aptly@b43e8473ef81:/app$ aptly version
|
||||||
|
aptly version: 1.5.0+189+g0fc90dff
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Running unit tests
|
||||||
|
|
||||||
|
In order to run aptly unit tests, enter the following:
|
||||||
|
```
|
||||||
|
make docker-unit-tests
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Running system tests
|
||||||
|
|
||||||
|
In order to run aptly system tests, enter the following:
|
||||||
|
```
|
||||||
|
make docker-system-tests
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Running golangci-lint
|
||||||
|
|
||||||
|
In order to run aptly unit tests, run:
|
||||||
|
```
|
||||||
|
make docker-lint
|
||||||
|
```
|
||||||
|
|
||||||
|
#### More info
|
||||||
|
|
||||||
|
Run `make help` for more information.
|
||||||
|
|
||||||
|
|
||||||
|
### Local Development Setup
|
||||||
|
|
||||||
|
This section describes local setup to start contributing to aptly.
|
||||||
|
|
||||||
|
#### Go & Python
|
||||||
|
|
||||||
|
You would need `Go` (latest version is recommended) and `Python` 3.9 (or newer, the CI currently tests against 3.11).
|
||||||
|
|
||||||
|
If you're new to Go, follow [getting started guide](https://golang.org/doc/install) to install it and perform
|
||||||
|
initial setup. With Go 1.8+, default `$GOPATH` is `$HOME/go`, so rest of this document assumes that.
|
||||||
|
|
||||||
|
Usually `$GOPATH/bin` is appended to your `$PATH` to make it easier to run built binaries, but you might choose
|
||||||
|
to prepend it or to skip this test if you're security conscious.
|
||||||
|
|
||||||
|
#### Dependencies
|
||||||
|
|
||||||
You would need some additional tools and Python virtual environment to run tests and checks, install them with:
|
You would need some additional tools and Python virtual environment to run tests and checks, install them with:
|
||||||
|
|
||||||
@@ -110,7 +183,7 @@ Aptly is using Go modules to manage dependencies, download modules using:
|
|||||||
|
|
||||||
make modules
|
make modules
|
||||||
|
|
||||||
### Building
|
#### Building
|
||||||
|
|
||||||
If you want to build aptly binary from your current source tree, run:
|
If you want to build aptly binary from your current source tree, run:
|
||||||
|
|
||||||
@@ -124,7 +197,7 @@ Or, if it's not on your path:
|
|||||||
|
|
||||||
~/go/bin/aptly
|
~/go/bin/aptly
|
||||||
|
|
||||||
### Unit-tests
|
#### Unit-tests
|
||||||
|
|
||||||
aptly has two kinds of tests: unit-tests and functional (system) tests. Functional tests are preferred way to test any
|
aptly has two kinds of tests: unit-tests and functional (system) tests. Functional tests are preferred way to test any
|
||||||
feature, but some features are much easier to test with unit-tests (e.g. algorithms, failure scenarios, ...)
|
feature, but some features are much easier to test with unit-tests (e.g. algorithms, failure scenarios, ...)
|
||||||
@@ -133,7 +206,7 @@ aptly is using standard Go unit-test infrastructure plus [gocheck](http://labix.
|
|||||||
|
|
||||||
make test
|
make test
|
||||||
|
|
||||||
### Functional Tests
|
#### Functional Tests
|
||||||
|
|
||||||
Functional tests are implemented in Python, and they use custom test runner which is similar to Python unit-test
|
Functional tests are implemented in Python, and they use custom test runner which is similar to Python unit-test
|
||||||
runner. Most of the tests start with clean aptly state, run some aptly commands to prepare environment, and finally
|
runner. Most of the tests start with clean aptly state, run some aptly commands to prepare environment, and finally
|
||||||
@@ -180,7 +253,7 @@ There are some packages available under `system/files/` directory which are used
|
|||||||
this default location. You can run aptly under different user or by using non-default config location with non-default
|
this default location. You can run aptly under different user or by using non-default config location with non-default
|
||||||
aptly root directory.
|
aptly root directory.
|
||||||
|
|
||||||
### Style Checks
|
#### Style Checks
|
||||||
|
|
||||||
Style checks could be run with:
|
Style checks could be run with:
|
||||||
|
|
||||||
@@ -191,7 +264,7 @@ for the linter could be found in [.golangci.yml](.golangci.yml) file.
|
|||||||
|
|
||||||
Python code (system tests) are linted with [flake8 tool](https://pypi.python.org/pypi/flake8).
|
Python code (system tests) are linted with [flake8 tool](https://pypi.python.org/pypi/flake8).
|
||||||
|
|
||||||
### Vendored Code
|
#### Vendored Code
|
||||||
|
|
||||||
aptly is using Go vendoring for all the libraries aptly depends upon. `vendor/` directory is checked into the source
|
aptly is using Go vendoring for all the libraries aptly depends upon. `vendor/` directory is checked into the source
|
||||||
repository to avoid any problems if source repositories go away. Go build process will automatically prefer vendored
|
repository to avoid any problems if source repositories go away. Go build process will automatically prefer vendored
|
||||||
@@ -217,25 +290,3 @@ Bash and Zsh completion for aptly reside in the same repo under in [completion.d
|
|||||||
When new option or command is introduced, bash completion should be updated to reflect that change.
|
When new option or command is introduced, bash completion should be updated to reflect that change.
|
||||||
|
|
||||||
When aptly package is being built, it automatically pulls bash completion and man page into the package.
|
When aptly package is being built, it automatically pulls bash completion and man page into the package.
|
||||||
|
|
||||||
## Design
|
|
||||||
|
|
||||||
This section requires future work.
|
|
||||||
|
|
||||||
*TBD*
|
|
||||||
|
|
||||||
### Database
|
|
||||||
|
|
||||||
### Package Pool
|
|
||||||
|
|
||||||
### Package
|
|
||||||
|
|
||||||
### PackageList, PackageRefList
|
|
||||||
|
|
||||||
### LocalRepo, RemoteRepo, Snapshot
|
|
||||||
|
|
||||||
### PublishedRepository
|
|
||||||
|
|
||||||
### Context
|
|
||||||
|
|
||||||
### Collections, CollectionFactory
|
|
||||||
|
|||||||
@@ -1,81 +1,188 @@
|
|||||||
GOVERSION=$(shell go version | awk '{print $$3;}')
|
GOPATH=$(shell go env GOPATH)
|
||||||
ifdef TRAVIS_TAG
|
VERSION=$(shell make -s version)
|
||||||
TAG=$(TRAVIS_TAG)
|
|
||||||
else
|
|
||||||
TAG="$(shell git describe --tags --always)"
|
|
||||||
endif
|
|
||||||
VERSION=$(shell echo $(TAG) | sed 's@^v@@' | sed 's@-@+@g')
|
|
||||||
PACKAGES=context database deb files gpg http query swift s3 utils
|
|
||||||
PYTHON?=python3
|
PYTHON?=python3
|
||||||
TESTS?=
|
|
||||||
BINPATH?=$(GOPATH)/bin
|
BINPATH?=$(GOPATH)/bin
|
||||||
RUN_LONG_TESTS?=yes
|
GOLANGCI_LINT_VERSION=v1.54.1 # version supporting go 1.19
|
||||||
|
COVERAGE_DIR?=$(shell mktemp -d)
|
||||||
|
GOOS=$(shell go env GOHOSTOS)
|
||||||
|
GOARCH=$(shell go env GOHOSTARCH)
|
||||||
|
|
||||||
all: modules test bench check system-test
|
# Uncomment to update test outputs
|
||||||
|
# CAPTURE := "--capture"
|
||||||
|
|
||||||
prepare:
|
help: ## Print this help
|
||||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.43.0
|
@grep -E '^[a-zA-Z][a-zA-Z0-9_-]*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
modules:
|
prepare: ## Install go module dependencies
|
||||||
go mod download
|
# Prepare go modules
|
||||||
go mod verify
|
go mod verify
|
||||||
go mod tidy -v
|
go mod tidy -v
|
||||||
|
# Generate VERSION file
|
||||||
|
go generate
|
||||||
|
|
||||||
dev:
|
releasetype: # Print release type: ci (on any branch/commit), release (on a tag)
|
||||||
go get -u github.com/laher/goxc
|
@reltype=ci ; \
|
||||||
|
gitbranch=`git rev-parse --abbrev-ref HEAD` ; \
|
||||||
|
if [ "$$gitbranch" = "HEAD" ] && [ "$$FORCE_CI" != "true" ]; then \
|
||||||
|
gittag=`git describe --tags --exact-match 2>/dev/null` ;\
|
||||||
|
if echo "$$gittag" | grep -q '^v[0-9]'; then \
|
||||||
|
reltype=release ; \
|
||||||
|
fi ; \
|
||||||
|
fi ; \
|
||||||
|
echo $$reltype
|
||||||
|
|
||||||
check: system/env
|
version: ## Print aptly version
|
||||||
ifeq ($(RUN_LONG_TESTS), yes)
|
@ci="" ; \
|
||||||
golangci-lint run
|
if [ "`make -s releasetype`" = "ci" ]; then \
|
||||||
system/env/bin/flake8
|
ci=`TZ=UTC git show -s --format='+%cd.%h' --date=format-local:'%Y%m%d%H%M%S'`; \
|
||||||
endif
|
fi ; \
|
||||||
|
if which dpkg-parsechangelog > /dev/null 2>&1; then \
|
||||||
|
echo `dpkg-parsechangelog -S Version`$$ci; \
|
||||||
|
else \
|
||||||
|
echo `grep ^aptly -m1 debian/changelog | sed 's/.*(\([^)]\+\)).*/\1/'`$$ci ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
swagger-install:
|
||||||
|
# Install swag
|
||||||
|
@test -f $(BINPATH)/swag || GOOS=linux GOARCH=amd64 go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
|
||||||
|
swagger: swagger-install
|
||||||
|
# Generate swagger docs
|
||||||
|
@PATH=$(BINPATH)/:$(PATH) swag init --markdownFiles docs
|
||||||
|
|
||||||
|
etcd-install:
|
||||||
|
# Install etcd
|
||||||
|
test -d /srv/etcd || system/t13_etcd/install-etcd.sh
|
||||||
|
|
||||||
|
flake8: ## run flake8 on system test python files
|
||||||
|
flake8 system/
|
||||||
|
|
||||||
|
lint:
|
||||||
|
# Install golangci-lint
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
|
||||||
|
# Running lint
|
||||||
|
@PATH=$(BINPATH)/:$(PATH) golangci-lint run
|
||||||
|
|
||||||
|
|
||||||
|
build: prepare swagger ## Build aptly
|
||||||
|
go build -o build/aptly
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go install -v -ldflags "-X main.Version=$(VERSION)"
|
@echo "\e[33m\e[1mBuilding aptly ...\e[0m"
|
||||||
|
go generate
|
||||||
|
@out=`mktemp`; if ! go install -v > $$out 2>&1; then cat $$out; rm -f $$out; echo "\nBuild failed\n"; exit 1; else rm -f $$out; fi
|
||||||
|
|
||||||
system/env: system/requirements.txt
|
test: prepare swagger etcd-install ## Run unit tests
|
||||||
ifeq ($(RUN_LONG_TESTS), yes)
|
@echo "\e[33m\e[1mStarting etcd ...\e[0m"
|
||||||
rm -rf system/env
|
@mkdir -p /tmp/etcd-data; system/t13_etcd/start-etcd.sh > /tmp/etcd-data/etcd.log 2>&1 &
|
||||||
$(PYTHON) -m venv system/env
|
@echo "\e[33m\e[1mRunning go test ...\e[0m"
|
||||||
system/env/bin/pip install -r system/requirements.txt
|
go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
|
||||||
endif
|
@echo "\e[33m\e[1mStopping etcd ...\e[0m"
|
||||||
|
@pid=`cat /tmp/etcd.pid`; kill $$pid
|
||||||
|
@rm -f /tmp/etcd-data/etcd.log
|
||||||
|
@ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret
|
||||||
|
|
||||||
system-test: install system/env
|
system-test: prepare swagger etcd-install ## Run system tests
|
||||||
ifeq ($(RUN_LONG_TESTS), yes)
|
# build coverage binary
|
||||||
|
go test -v -coverpkg="./..." -c -tags testruncli
|
||||||
|
# Download fixture-db, fixture-pool, etcd.db
|
||||||
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
|
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
|
||||||
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
|
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
|
||||||
PATH=$(BINPATH)/:$(PATH) && . system/env/bin/activate && APTLY_VERSION=$(VERSION) $(PYTHON) system/run.py --long $(TESTS)
|
test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz)
|
||||||
endif
|
# Run system tests
|
||||||
|
PATH=$(BINPATH)/:$(PATH) && FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE) $(TEST)
|
||||||
test:
|
|
||||||
go test -v ./... -gocheck.v=true -race -coverprofile=coverage.txt -covermode=atomic
|
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
|
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
|
||||||
go test -v ./deb -run=nothing -bench=. -benchmem
|
go test -v ./deb -run=nothing -bench=. -benchmem
|
||||||
|
|
||||||
|
serve: prepare swagger-install ## Run development server (auto recompiling)
|
||||||
|
test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3
|
||||||
|
cp debian/aptly.conf ~/.aptly.conf
|
||||||
|
sed -i /enableSwaggerEndpoint/s/false/true/ ~/.aptly.conf
|
||||||
|
PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,cmd,systemd -- api serve -listen 0.0.0.0:3142
|
||||||
|
|
||||||
|
dpkg: prepare swagger ## Build debian packages
|
||||||
|
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
|
||||||
|
# set debian version
|
||||||
|
@if [ "`make -s releasetype`" = "ci" ]; then \
|
||||||
|
echo CI Build, setting version... ; \
|
||||||
|
cp debian/changelog debian/changelog.dpkg-bak ; \
|
||||||
|
DEBEMAIL="CI <ci@aptly>" dch -v `make -s version` "CI build" ; \
|
||||||
|
fi
|
||||||
|
# Run dpkg-buildpackage
|
||||||
|
buildtype="any" ; \
|
||||||
|
if [ "$(DEBARCH)" = "amd64" ]; then \
|
||||||
|
buildtype="any,all" ; \
|
||||||
|
fi ; \
|
||||||
|
echo "\e[33m\e[1mBuilding: $$buildtype\e[0m" ; \
|
||||||
|
dpkg-buildpackage -us -uc --build=$$buildtype -d --host-arch=$(DEBARCH)
|
||||||
|
# cleanup
|
||||||
|
@test -f debian/changelog.dpkg-bak && mv debian/changelog.dpkg-bak debian/changelog || true ; \
|
||||||
|
mkdir -p build && mv ../*.deb build/ ; \
|
||||||
|
cd build && ls -l *.deb
|
||||||
|
|
||||||
|
binaries: prepare swagger ## Build binary releases (FreeBSD, MacOS, Linux tar)
|
||||||
|
# build aptly
|
||||||
|
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o build/tmp/aptly -ldflags='-extldflags=-static'
|
||||||
|
# install
|
||||||
|
@mkdir -p build/tmp/man build/tmp/completion/bash_completion.d build/tmp/completion/zsh/vendor-completions
|
||||||
|
@cp man/aptly.1 build/tmp/man/
|
||||||
|
@cp completion.d/aptly build/tmp/completion/bash_completion.d/
|
||||||
|
@cp completion.d/_aptly build/tmp/completion/zsh/vendor-completions/
|
||||||
|
@cp README.rst LICENSE AUTHORS build/tmp/
|
||||||
|
@gzip -f build/tmp/man/aptly.1
|
||||||
|
@path="aptly_$(VERSION)_$(GOOS)_$(GOARCH)"; \
|
||||||
|
rm -rf "build/$$path"; \
|
||||||
|
mv build/tmp build/"$$path"; \
|
||||||
|
rm -rf build/tmp; \
|
||||||
|
cd build; \
|
||||||
|
zip -r "$$path".zip "$$path" > /dev/null \
|
||||||
|
&& echo "Built build/$${path}.zip"; \
|
||||||
|
rm -rf "$$path"
|
||||||
|
|
||||||
|
docker-image: ## Build aptly-dev docker image
|
||||||
|
@docker build -f system/Dockerfile . -t aptly-dev
|
||||||
|
|
||||||
|
docker-build: ## Build aptly in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper build
|
||||||
|
|
||||||
|
docker-shell: ## Run aptly and other commands in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper || true
|
||||||
|
|
||||||
|
docker-deb: ## Build debian packages in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64
|
||||||
|
|
||||||
|
docker-unit-test: ## Run unit tests in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper test
|
||||||
|
|
||||||
|
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 system-test TEST=$(TEST)
|
||||||
|
|
||||||
|
docker-serve: ## Run development server (auto recompiling) on http://localhost:3142
|
||||||
|
@docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper serve || true
|
||||||
|
|
||||||
|
docker-lint: ## Run golangci-lint in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper lint
|
||||||
|
|
||||||
|
docker-binaries: ## Build binary releases (FreeBSD, MacOS, Linux tar) in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper binaries
|
||||||
|
|
||||||
|
docker-man: ## Create man page in docker container
|
||||||
|
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper man
|
||||||
|
|
||||||
mem.png: mem.dat mem.gp
|
mem.png: mem.dat mem.gp
|
||||||
gnuplot mem.gp
|
gnuplot mem.gp
|
||||||
open mem.png
|
open mem.png
|
||||||
|
|
||||||
goxc: dev
|
man: ## Create man pages
|
||||||
rm -rf root/
|
|
||||||
mkdir -p root/usr/share/man/man1/ root/etc/bash_completion.d/ root/usr/share/zsh/vendor-completions/
|
|
||||||
cp man/aptly.1 root/usr/share/man/man1
|
|
||||||
cp completion.d/aptly root/etc/bash_completion.d/
|
|
||||||
cp completion.d/_aptly root/usr/share/zsh/vendor-completions/
|
|
||||||
gzip root/usr/share/man/man1/aptly.1
|
|
||||||
goxc -pv=$(VERSION) -max-processors=2 $(GOXC_OPTS)
|
|
||||||
|
|
||||||
release: GOXC_OPTS=-tasks-=bintray,go-vet,go-test,rmbin
|
|
||||||
release: goxc
|
|
||||||
rm -rf build/
|
|
||||||
mkdir -p build/
|
|
||||||
mv xc-out/$(VERSION)/aptly_$(VERSION)_* build/
|
|
||||||
|
|
||||||
man:
|
|
||||||
make -C man
|
make -C man
|
||||||
|
|
||||||
version:
|
clean: ## remove local build and module cache
|
||||||
@echo $(VERSION)
|
# Clean all generated and build files
|
||||||
|
test -d .go/ && chmod u+w -R .go/ && rm -rf .go/ || true
|
||||||
|
rm -rf build/ obj-*-linux-gnu* tmp/
|
||||||
|
rm -f unit.out aptly.test VERSION docs/docs.go docs/swagger.json docs/swagger.yaml docs/swagger.conf
|
||||||
|
find system/ -type d -name __pycache__ -exec rm -rf {} \; 2>/dev/null || true
|
||||||
|
|
||||||
.PHONY: man modules version release goxc
|
.PHONY: help man prepare swagger version binaries docker-release docker-system-test docker-unit-test docker-lint docker-build docker-image build docker-shell clean releasetype dpkg serve docker-serve flake8
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ aptly
|
|||||||
:target: https://codecov.io/gh/aptly-dev/aptly
|
:target: https://codecov.io/gh/aptly-dev/aptly
|
||||||
|
|
||||||
.. image:: https://badges.gitter.im/Join Chat.svg
|
.. image:: https://badges.gitter.im/Join Chat.svg
|
||||||
:target: https://gitter.im/aptly-dev/aptly?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
:target: https://matrix.to/#/#aptly:gitter.im
|
||||||
|
|
||||||
.. image:: https://goreportcard.com/badge/github.com/aptly-dev/aptly
|
.. image:: https://goreportcard.com/badge/github.com/aptly-dev/aptly
|
||||||
:target: https://goreportcard.com/report/aptly-dev/aptly
|
:target: https://goreportcard.com/report/aptly-dev/aptly
|
||||||
|
|||||||
+74
-12
@@ -3,17 +3,18 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/deb"
|
"github.com/aptly-dev/aptly/deb"
|
||||||
"github.com/aptly-dev/aptly/query"
|
"github.com/aptly-dev/aptly/query"
|
||||||
"github.com/aptly-dev/aptly/task"
|
"github.com/aptly-dev/aptly/task"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lock order acquisition (canonical):
|
// Lock order acquisition (canonical):
|
||||||
@@ -27,6 +28,23 @@ func apiVersion(c *gin.Context) {
|
|||||||
c.JSON(200, gin.H{"Version": aptly.Version})
|
c.JSON(200, gin.H{"Version": aptly.Version})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/ready
|
||||||
|
func apiReady(isReady *atomic.Value) func(*gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if isReady == nil || !isReady.Load().(bool) {
|
||||||
|
c.JSON(503, gin.H{"Status": "Aptly is unavailable"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"Status": "Aptly is ready"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/healthy
|
||||||
|
func apiHealthy(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{"Status": "Aptly is healthy"})
|
||||||
|
}
|
||||||
|
|
||||||
type dbRequestKind int
|
type dbRequestKind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -137,20 +155,29 @@ func maybeRunTaskInBackground(c *gin.Context, name string, resources []string, p
|
|||||||
// Run this task in background if configured globally or per-request
|
// Run this task in background if configured globally or per-request
|
||||||
background := truthy(c.DefaultQuery("_async", strconv.FormatBool(context.Config().AsyncAPI)))
|
background := truthy(c.DefaultQuery("_async", strconv.FormatBool(context.Config().AsyncAPI)))
|
||||||
if background {
|
if background {
|
||||||
log.Println("Executing task asynchronously")
|
log.Debug().Msg("Executing task asynchronously")
|
||||||
task, conflictErr := runTaskInBackground(name, resources, proc)
|
task, conflictErr := runTaskInBackground(name, resources, proc)
|
||||||
if conflictErr != nil {
|
if conflictErr != nil {
|
||||||
c.AbortWithError(409, conflictErr)
|
AbortWithJSONError(c, 409, conflictErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(202, task)
|
c.JSON(202, task)
|
||||||
} else {
|
} else {
|
||||||
log.Println("Executing task synchronously")
|
log.Debug().Msg("Executing task synchronously")
|
||||||
out := context.Progress()
|
task, conflictErr := runTaskInBackground(name, resources, proc)
|
||||||
detail := task.Detail{}
|
if conflictErr != nil {
|
||||||
retValue, err := proc(out, &detail)
|
AbortWithJSONError(c, 409, conflictErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for task to finish
|
||||||
|
context.TaskList().WaitForTaskByID(task.ID)
|
||||||
|
|
||||||
|
retValue, _ := context.TaskList().GetTaskReturnValueByID(task.ID)
|
||||||
|
err, _ := context.TaskList().GetTaskErrorByID(task.ID)
|
||||||
|
context.TaskList().DeleteTaskByID(task.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(retValue.Code, err)
|
AbortWithJSONError(c, retValue.Code, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if retValue != nil {
|
if retValue != nil {
|
||||||
@@ -168,7 +195,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
|
|
||||||
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil)
|
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +203,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
if queryS != "" {
|
if queryS != "" {
|
||||||
q, err := query.Parse(c.Request.URL.Query().Get("q"))
|
q, err := query.Parse(c.Request.URL.Query().Get("q"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +220,7 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
sort.Strings(architecturesList)
|
sort.Strings(architecturesList)
|
||||||
|
|
||||||
if len(architecturesList) == 0 {
|
if len(architecturesList) == 0 {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,11 +230,41 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
|
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
|
||||||
nil, context.DependencyOptions(), architecturesList)
|
nil, context.DependencyOptions(), architecturesList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to search: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter packages by version
|
||||||
|
if c.Request.URL.Query().Get("maximumVersion") == "1" {
|
||||||
|
list.PrepareIndex()
|
||||||
|
list.ForEach(func(p *deb.Package) error {
|
||||||
|
versionQ, err := query.Parse(fmt.Sprintf("Name (%s), $Version (<= %s)", p.Name, p.Version))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("filter packages by version, query string parse err: ", err)
|
||||||
|
c.AbortWithError(500, fmt.Errorf("unable to parse %s maximum version query string: %s", p.Name, err))
|
||||||
|
} else {
|
||||||
|
tmpList, err := list.Filter([]deb.PackageQuery{versionQ}, false,
|
||||||
|
nil, 0, []string{})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if tmpList.Len() > 0 {
|
||||||
|
tmpList.ForEach(func(tp *deb.Package) error {
|
||||||
|
list.Remove(tp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
list.Add(p)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("filter packages by version, filter err: ", err)
|
||||||
|
c.AbortWithError(500, fmt.Errorf("unable to get %s maximum version: %s", p.Name, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if c.Request.URL.Query().Get("format") == "details" {
|
if c.Request.URL.Query().Get("format") == "details" {
|
||||||
list.ForEach(func(p *deb.Package) error {
|
list.ForEach(func(p *deb.Package) error {
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
@@ -219,3 +276,8 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
|||||||
c.JSON(200, list.Strings())
|
c.JSON(200, list.Strings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AbortWithJSONError(c *gin.Context, code int, err error) *gin.Error {
|
||||||
|
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
return c.AbortWithError(code, err)
|
||||||
|
}
|
||||||
|
|||||||
+67
-9
@@ -1,16 +1,20 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
ctx "github.com/aptly-dev/aptly/context"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
ctx "github.com/aptly-dev/aptly/context"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/smira/flag"
|
"github.com/smira/flag"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
@@ -30,12 +34,13 @@ type ApiSuite struct {
|
|||||||
var _ = Suite(&ApiSuite{})
|
var _ = Suite(&ApiSuite{})
|
||||||
|
|
||||||
func createTestConfig() *os.File {
|
func createTestConfig() *os.File {
|
||||||
file, err := ioutil.TempFile("", "aptly")
|
file, err := os.CreateTemp("", "aptly")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
jsonString, err := json.Marshal(gin.H{
|
jsonString, err := json.Marshal(gin.H{
|
||||||
"architectures": []string{},
|
"architectures": []string{},
|
||||||
|
"enableMetricsEndpoint": true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -44,9 +49,12 @@ func createTestConfig() *os.File {
|
|||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) SetUpSuite(c *C) {
|
func (s *ApiSuite) setupContext() error {
|
||||||
|
aptly.Version = "testVersion"
|
||||||
file := createTestConfig()
|
file := createTestConfig()
|
||||||
c.Assert(file, NotNil)
|
if nil == file {
|
||||||
|
return fmt.Errorf("unable to create the test configuration file")
|
||||||
|
}
|
||||||
s.configFile = file
|
s.configFile = file
|
||||||
|
|
||||||
flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
|
flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
|
||||||
@@ -57,10 +65,19 @@ func (s *ApiSuite) SetUpSuite(c *C) {
|
|||||||
s.flags = flags
|
s.flags = flags
|
||||||
|
|
||||||
context, err := ctx.NewContext(s.flags)
|
context, err := ctx.NewContext(s.flags)
|
||||||
c.Assert(err, IsNil)
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
s.context = context
|
s.context = context
|
||||||
s.router = Router(context)
|
s.router = Router(context)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApiSuite) SetUpSuite(c *C) {
|
||||||
|
err := s.setupContext()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TearDownSuite(c *C) {
|
func (s *ApiSuite) TearDownSuite(c *C) {
|
||||||
@@ -85,11 +102,52 @@ func (s *ApiSuite) HTTPRequest(method string, url string, body io.Reader) (*http
|
|||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ApiSuite) TestGinRunsInReleaseMode(c *C) {
|
||||||
|
c.Check(gin.Mode(), Equals, gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestGetVersion(c *C) {
|
func (s *ApiSuite) TestGetVersion(c *C) {
|
||||||
response, err := s.HTTPRequest("GET", "/api/version", nil)
|
response, err := s.HTTPRequest("GET", "/api/version", nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
c.Check(response.Body.String(), Matches, ".*Version.*")
|
c.Check(response.Body.String(), Matches, "{\"Version\":\""+aptly.Version+"\"}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApiSuite) TestGetReadiness(c *C) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/ready", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is ready\"}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApiSuite) TestGetHealthiness(c *C) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/healthy", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is healthy\"}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApiSuite) TestGetMetrics(c *C) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/metrics", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
b := strings.Replace(response.Body.String(), "\n", "", -1)
|
||||||
|
c.Check(b, Matches, ".*# TYPE aptly_api_http_requests_in_flight gauge.*")
|
||||||
|
c.Check(b, Matches, ".*# TYPE aptly_api_http_requests_total counter.*")
|
||||||
|
c.Check(b, Matches, ".*# TYPE aptly_api_http_request_size_bytes summary.*")
|
||||||
|
c.Check(b, Matches, ".*# TYPE aptly_api_http_response_size_bytes summary.*")
|
||||||
|
c.Check(b, Matches, ".*# TYPE aptly_api_http_request_duration_seconds summary.*")
|
||||||
|
c.Check(b, Matches, ".*# TYPE aptly_build_info gauge.*")
|
||||||
|
c.Check(b, Matches, ".*aptly_build_info.*version=\"testVersion\".*")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApiSuite) TestRepoCreate(c *C) {
|
||||||
|
body, err := json.Marshal(gin.H{
|
||||||
|
"Name": "dummy",
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiSuite) TestTruthy(c *C) {
|
func (s *ApiSuite) TestTruthy(c *C) {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
+37
-24
@@ -6,8 +6,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/saracen/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func verifyPath(path string) bool {
|
func verifyPath(path string) bool {
|
||||||
@@ -24,27 +27,31 @@ func verifyPath(path string) bool {
|
|||||||
|
|
||||||
func verifyDir(c *gin.Context) bool {
|
func verifyDir(c *gin.Context) bool {
|
||||||
if !verifyPath(c.Params.ByName("dir")) {
|
if !verifyPath(c.Params.ByName("dir")) {
|
||||||
c.AbortWithError(400, fmt.Errorf("wrong dir"))
|
AbortWithJSONError(c, 400, fmt.Errorf("wrong dir"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /files
|
// @Summary Get files
|
||||||
|
// @Description Get list of uploaded files.
|
||||||
|
// @Tags Files
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} string "List of files"
|
||||||
|
// @Router /api/files [get]
|
||||||
func apiFilesListDirs(c *gin.Context) {
|
func apiFilesListDirs(c *gin.Context) {
|
||||||
list := []string{}
|
list := []string{}
|
||||||
|
listLock := &sync.Mutex{}
|
||||||
|
|
||||||
err := filepath.Walk(context.UploadPath(), func(path string, info os.FileInfo, err error) error {
|
err := walker.Walk(context.UploadPath(), func(path string, info os.FileInfo) error {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if path == context.UploadPath() {
|
if path == context.UploadPath() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
|
listLock.Lock()
|
||||||
|
defer listLock.Unlock()
|
||||||
list = append(list, filepath.Base(path))
|
list = append(list, filepath.Base(path))
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
@@ -53,7 +60,7 @@ func apiFilesListDirs(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,17 +73,17 @@ func apiFilesUpload(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(context.UploadPath(), c.Params.ByName("dir"))
|
path := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir")))
|
||||||
err := os.MkdirAll(path, 0777)
|
err := os.MkdirAll(path, 0777)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Request.ParseMultipartForm(10 * 1024 * 1024)
|
err = c.Request.ParseMultipartForm(10 * 1024 * 1024)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +93,7 @@ func apiFilesUpload(c *gin.Context) {
|
|||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
src, err := file.Open()
|
src, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer src.Close()
|
defer src.Close()
|
||||||
@@ -94,14 +101,14 @@ func apiFilesUpload(c *gin.Context) {
|
|||||||
destPath := filepath.Join(path, filepath.Base(file.Filename))
|
destPath := filepath.Join(path, filepath.Base(file.Filename))
|
||||||
dst, err := os.Create(destPath)
|
dst, err := os.Create(destPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer dst.Close()
|
defer dst.Close()
|
||||||
|
|
||||||
_, err = io.Copy(dst, src)
|
_, err = io.Copy(dst, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +116,7 @@ func apiFilesUpload(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc()
|
||||||
c.JSON(200, stored)
|
c.JSON(200, stored)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -120,9 +128,10 @@ func apiFilesListFiles(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list := []string{}
|
list := []string{}
|
||||||
root := filepath.Join(context.UploadPath(), c.Params.ByName("dir"))
|
listLock := &sync.Mutex{}
|
||||||
|
root := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir")))
|
||||||
|
|
||||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(root, func(path string, _ os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -131,6 +140,8 @@ func apiFilesListFiles(c *gin.Context) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listLock.Lock()
|
||||||
|
defer listLock.Unlock()
|
||||||
list = append(list, filepath.Base(path))
|
list = append(list, filepath.Base(path))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -138,9 +149,9 @@ func apiFilesListFiles(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -154,9 +165,9 @@ func apiFilesDeleteDir(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.RemoveAll(filepath.Join(context.UploadPath(), c.Params.ByName("dir")))
|
err := os.RemoveAll(filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir"))))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,15 +180,17 @@ func apiFilesDeleteFile(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verifyPath(c.Params.ByName("name")) {
|
dir := utils.SanitizePath(c.Params.ByName("dir"))
|
||||||
c.AbortWithError(400, fmt.Errorf("wrong file"))
|
name := utils.SanitizePath(c.Params.ByName("name"))
|
||||||
|
if !verifyPath(name) {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("wrong file"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.Remove(filepath.Join(context.UploadPath(), c.Params.ByName("dir"), c.Params.ByName("name")))
|
err := os.Remove(filepath.Join(context.UploadPath(), dir, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) {
|
if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -2,13 +2,13 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/pgp"
|
"github.com/aptly-dev/aptly/pgp"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,9 +24,13 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
if c.Bind(&b) != nil {
|
if c.Bind(&b) != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
b.Keyserver = utils.SanitizePath(b.Keyserver)
|
||||||
|
b.GpgKeyID = utils.SanitizePath(b.GpgKeyID)
|
||||||
|
b.GpgKeyArmor = utils.SanitizePath(b.GpgKeyArmor)
|
||||||
|
// b.Keyring can be an absolute path
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
args := []string{"--no-default-keyring"}
|
args := []string{"--no-default-keyring", "--allow-non-selfsigned-uid"}
|
||||||
keyring := "trustedkeys.gpg"
|
keyring := "trustedkeys.gpg"
|
||||||
if len(b.Keyring) > 0 {
|
if len(b.Keyring) > 0 {
|
||||||
keyring = b.Keyring
|
keyring = b.Keyring
|
||||||
@@ -37,9 +41,9 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if len(b.GpgKeyArmor) > 0 {
|
if len(b.GpgKeyArmor) > 0 {
|
||||||
var tempdir string
|
var tempdir string
|
||||||
tempdir, err = ioutil.TempDir(os.TempDir(), "aptly")
|
tempdir, err = os.MkdirTemp(os.TempDir(), "aptly")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tempdir)
|
defer os.RemoveAll(tempdir)
|
||||||
@@ -47,11 +51,11 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
keypath := filepath.Join(tempdir, "key")
|
keypath := filepath.Join(tempdir, "key")
|
||||||
keyfile, e := os.Create(keypath)
|
keyfile, e := os.Create(keypath)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
c.AbortWithError(400, e)
|
AbortWithJSONError(c, 400, e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, e = keyfile.WriteString(b.GpgKeyArmor); e != nil {
|
if _, e = keyfile.WriteString(b.GpgKeyArmor); e != nil {
|
||||||
c.AbortWithError(400, e)
|
AbortWithJSONError(c, 400, e)
|
||||||
}
|
}
|
||||||
args = append(args, "--import", keypath)
|
args = append(args, "--import", keypath)
|
||||||
|
|
||||||
@@ -62,10 +66,10 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
args = append(args, keys...)
|
args = append(args, keys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
finder := pgp.GPG1Finder()
|
finder := pgp.GPGDefaultFinder()
|
||||||
gpg, _, err := finder.FindGPG()
|
gpg, _, err := finder.FindGPG()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +78,11 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
// there is no error handling for such as gpg will do this for us
|
// there is no error handling for such as gpg will do this for us
|
||||||
cmd := exec.Command(gpg, args...)
|
cmd := exec.Command(gpg, args...)
|
||||||
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
|
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
|
||||||
cmd.Stdout = os.Stdout
|
out, err := cmd.CombinedOutput()
|
||||||
if err = cmd.Run(); err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
c.JSON(400, string(out))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{})
|
c.JSON(200, string(out))
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -43,25 +43,25 @@ func apiGraph(c *gin.Context) {
|
|||||||
|
|
||||||
stdin, err := command.StdinPipe()
|
stdin, err := command.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(stdin, buf)
|
_, err = io.Copy(stdin, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = stdin.Close()
|
err = stdin.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err = command.Output()
|
output, err = command.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiRequestsInFlightGauge = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "aptly_api_http_requests_in_flight",
|
||||||
|
Help: "Number of concurrent HTTP api requests currently handled.",
|
||||||
|
},
|
||||||
|
[]string{"method", "path"},
|
||||||
|
)
|
||||||
|
apiRequestsTotalCounter = promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "aptly_api_http_requests_total",
|
||||||
|
Help: "Total number of api requests.",
|
||||||
|
},
|
||||||
|
[]string{"code", "method", "path"},
|
||||||
|
)
|
||||||
|
apiRequestSizeSummary = promauto.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Name: "aptly_api_http_request_size_bytes",
|
||||||
|
Help: "Api HTTP request size in bytes.",
|
||||||
|
},
|
||||||
|
[]string{"code", "method", "path"},
|
||||||
|
)
|
||||||
|
apiResponseSizeSummary = promauto.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Name: "aptly_api_http_response_size_bytes",
|
||||||
|
Help: "Api HTTP response size in bytes.",
|
||||||
|
},
|
||||||
|
[]string{"code", "method", "path"},
|
||||||
|
)
|
||||||
|
apiRequestsDurationSummary = promauto.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Name: "aptly_api_http_request_duration_seconds",
|
||||||
|
Help: "Duration of api requests in seconds.",
|
||||||
|
},
|
||||||
|
[]string{"code", "method", "path"},
|
||||||
|
)
|
||||||
|
apiVersionGauge = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "aptly_build_info",
|
||||||
|
Help: "Metric with a constant '1' value labeled by version and goversion from which aptly was built.",
|
||||||
|
},
|
||||||
|
[]string{"version", "goversion"},
|
||||||
|
)
|
||||||
|
apiFilesUploadedCounter = promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "aptly_api_files_uploaded_total",
|
||||||
|
Help: "Total number of uploaded files labeled by upload directory.",
|
||||||
|
},
|
||||||
|
[]string{"directory"},
|
||||||
|
)
|
||||||
|
apiReposPackageCountGauge = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "aptly_repos_package_count",
|
||||||
|
Help: "Current number of published packages labeled by source, distribution and component.",
|
||||||
|
},
|
||||||
|
[]string{"source", "distribution", "component"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
type metricsCollectorRegistrar struct {
|
||||||
|
hasRegistered bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *metricsCollectorRegistrar) Register(router *gin.Engine) {
|
||||||
|
if !r.hasRegistered {
|
||||||
|
apiVersionGauge.WithLabelValues(aptly.Version, runtime.Version()).Set(1)
|
||||||
|
router.Use(instrumentHandlerInFlight(apiRequestsInFlightGauge, getBasePath))
|
||||||
|
router.Use(instrumentHandlerCounter(apiRequestsTotalCounter, getBasePath))
|
||||||
|
router.Use(instrumentHandlerRequestSize(apiRequestSizeSummary, getBasePath))
|
||||||
|
router.Use(instrumentHandlerResponseSize(apiResponseSizeSummary, getBasePath))
|
||||||
|
router.Use(instrumentHandlerDuration(apiRequestsDurationSummary, getBasePath))
|
||||||
|
r.hasRegistered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var MetricsCollectorRegistrar = metricsCollectorRegistrar{hasRegistered: false}
|
||||||
|
|
||||||
|
func countPackagesByRepos() {
|
||||||
|
err := context.NewCollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
||||||
|
err := context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.NewCollectionFactory())
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf(
|
||||||
|
"Error %s found while determining package count for metrics endpoint (prefix:%s / distribution:%s / component:%s\n).",
|
||||||
|
err, repo.StoragePrefix(), repo.Distribution, repo.Components())
|
||||||
|
log.Warn().Msg(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
components := repo.Components()
|
||||||
|
for _, c := range components {
|
||||||
|
count := float64(len(repo.RefList(c).Refs))
|
||||||
|
apiReposPackageCountGauge.WithLabelValues(fmt.Sprintf("%s", (repo.SourceNames())), repo.Distribution, c).Set(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("Error %s found while listing published repos for metrics endpoint", err)
|
||||||
|
log.Warn().Msg(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-45
@@ -9,59 +9,36 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
apiRequestsInFlightGauge = promauto.NewGaugeVec(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Name: "aptly_api_http_requests_in_flight",
|
|
||||||
Help: "Number of concurrent HTTP api requests currently handled.",
|
|
||||||
},
|
|
||||||
[]string{"method", "path"},
|
|
||||||
)
|
|
||||||
apiRequestsTotalCounter = promauto.NewCounterVec(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Name: "aptly_api_http_requests_total",
|
|
||||||
Help: "Total number of api requests.",
|
|
||||||
},
|
|
||||||
[]string{"code", "method", "path"},
|
|
||||||
)
|
|
||||||
apiRequestSizeSummary = promauto.NewSummaryVec(
|
|
||||||
prometheus.SummaryOpts{
|
|
||||||
Name: "aptly_api_http_request_size_bytes",
|
|
||||||
Help: "Api HTTP request size in bytes.",
|
|
||||||
},
|
|
||||||
[]string{"code", "method", "path"},
|
|
||||||
)
|
|
||||||
apiResponseSizeSummary = promauto.NewSummaryVec(
|
|
||||||
prometheus.SummaryOpts{
|
|
||||||
Name: "aptly_api_http_response_size_bytes",
|
|
||||||
Help: "Api HTTP response size in bytes.",
|
|
||||||
},
|
|
||||||
[]string{"code", "method", "path"},
|
|
||||||
)
|
|
||||||
apiRequestsDurationSummary = promauto.NewSummaryVec(
|
|
||||||
prometheus.SummaryOpts{
|
|
||||||
Name: "aptly_api_http_request_duration_seconds",
|
|
||||||
Help: "Duration of api requests in seconds.",
|
|
||||||
},
|
|
||||||
[]string{"code", "method", "path"},
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Only use base path as label value (e.g.: /api/repos) because of time series cardinality
|
// Only use base path as label value (e.g.: /api/repos) because of time series cardinality
|
||||||
// See https://prometheus.io/docs/practices/naming/#labels
|
// See https://prometheus.io/docs/practices/naming/#labels
|
||||||
func getBasePath(c *gin.Context) string {
|
func getBasePath(c *gin.Context) string {
|
||||||
return fmt.Sprintf("%s%s", getURLSegment(c.Request.URL.Path, 0), getURLSegment(c.Request.URL.Path, 1))
|
segment0, err := getURLSegment(c.Request.URL.Path, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
segment1, err := getURLSegment(c.Request.URL.Path, 1)
|
||||||
|
if err != nil {
|
||||||
|
return *segment0
|
||||||
|
}
|
||||||
|
|
||||||
|
return *segment0 + *segment1
|
||||||
}
|
}
|
||||||
|
|
||||||
func getURLSegment(url string, idx int) string {
|
func getURLSegment(url string, idx int) (*string, error) {
|
||||||
var urlSegments = strings.Split(url, "/")
|
urlSegments := strings.Split(url, "/")
|
||||||
|
|
||||||
// Remove segment at index 0 because it's an empty string
|
// Remove segment at index 0 because it's an empty string
|
||||||
var segmentAtIndex = urlSegments[1:cap(urlSegments)][idx]
|
urlSegments = urlSegments[1:cap(urlSegments)]
|
||||||
return fmt.Sprintf("/%s", segmentAtIndex)
|
|
||||||
|
if len(urlSegments) <= idx {
|
||||||
|
return nil, fmt.Errorf("index %d out of range, only has %d url segments", idx, len(urlSegments))
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentAtIndex := urlSegments[idx]
|
||||||
|
s := fmt.Sprintf("/%s", segmentAtIndex)
|
||||||
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func instrumentHandlerInFlight(g *prometheus.GaugeVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
func instrumentHandlerInFlight(g *prometheus.GaugeVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
||||||
@@ -101,3 +78,39 @@ func instrumentHandlerDuration(obs prometheus.ObserverVec, pathFunc func(*gin.Co
|
|||||||
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(time.Since(now).Seconds())
|
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(time.Since(now).Seconds())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSONLogger is a gin middleware that takes an instance of Logger and uses it for writing access
|
||||||
|
// logs that include error messages if there are any.
|
||||||
|
func JSONLogger() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Start timer
|
||||||
|
start := time.Now()
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
raw := c.Request.URL.RawQuery
|
||||||
|
|
||||||
|
// Process request
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
if raw != "" {
|
||||||
|
path = path + "?" + raw
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage := strings.TrimSuffix(c.Errors.ByType(gin.ErrorTypePrivate).String(), "\n")
|
||||||
|
l := log.With().Str("remote", c.ClientIP()).Logger().
|
||||||
|
With().Str("method", c.Request.Method).Logger().
|
||||||
|
With().Str("path", path).Logger().
|
||||||
|
With().Str("protocol", c.Request.Proto).Logger().
|
||||||
|
With().Str("code", fmt.Sprint(c.Writer.Status())).Logger().
|
||||||
|
With().Str("latency", ts.Sub(start).String()).Logger().
|
||||||
|
With().Str("agent", c.Request.UserAgent()).Logger()
|
||||||
|
|
||||||
|
if c.Writer.Status() >= 400 && c.Writer.Status() < 500 {
|
||||||
|
l.Warn().Msg(errorMessage)
|
||||||
|
} else if c.Writer.Status() >= 500 {
|
||||||
|
l.Error().Msg(errorMessage)
|
||||||
|
} else {
|
||||||
|
l.Info().Msg(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiddlewareSuite struct {
|
||||||
|
router http.Handler
|
||||||
|
context *gin.Context
|
||||||
|
logReader *os.File
|
||||||
|
logWriter *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&MiddlewareSuite{})
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) SetUpTest(c *C) {
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
utils.SetupJSONLogger("debug", w)
|
||||||
|
mw := JSONLogger()
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.UseRawPath = true
|
||||||
|
router.Use(mw)
|
||||||
|
router.Use(gin.Recovery(), gin.ErrorLogger())
|
||||||
|
|
||||||
|
root := router.Group("/api")
|
||||||
|
isReady := &atomic.Value{}
|
||||||
|
isReady.Store(false)
|
||||||
|
root.GET("/ready", apiReady(isReady))
|
||||||
|
root.GET("/healthy", apiHealthy)
|
||||||
|
|
||||||
|
s.router = router
|
||||||
|
s.logReader = r
|
||||||
|
s.logWriter = w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TearDownTest(c *C) {
|
||||||
|
s.router = nil
|
||||||
|
s.context = nil
|
||||||
|
s.logReader = nil
|
||||||
|
s.logWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) HTTPRequest(method string, url string, body io.Reader) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
s.context, _ = gin.CreateTestContext(recorder)
|
||||||
|
req, _ := http.NewRequestWithContext(s.context, method, url, body)
|
||||||
|
s.context.Request = req
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
s.router.ServeHTTP(httptest.NewRecorder(), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestJSONMiddleware4xx(c *C) {
|
||||||
|
outC := make(chan string)
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, s.logReader)
|
||||||
|
fmt.Println(buf.String())
|
||||||
|
outC <- buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/", nil)
|
||||||
|
s.logWriter.Close()
|
||||||
|
capturedOutput := <-outC
|
||||||
|
|
||||||
|
var jsonMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
|
if val, ok := jsonMap["level"]; ok {
|
||||||
|
c.Check(val, Equals, "warn")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := jsonMap["method"]; ok {
|
||||||
|
c.Check(val, Equals, "GET")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'method' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := jsonMap["path"]; ok {
|
||||||
|
c.Check(val, Equals, "/")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'path' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := jsonMap["protocol"]; ok {
|
||||||
|
c.Check(val, Equals, "HTTP/1.1")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'protocol' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := jsonMap["code"]; ok {
|
||||||
|
c.Check(val, Equals, "404")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'code' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := jsonMap["remote"]; !ok {
|
||||||
|
c.Errorf("Log message didn't have a 'remote' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := jsonMap["latency"]; !ok {
|
||||||
|
c.Errorf("Log message didn't have a 'latency' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := jsonMap["agent"]; !ok {
|
||||||
|
c.Errorf("Log message didn't have a 'agent' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := jsonMap["time"]; !ok {
|
||||||
|
c.Errorf("Log message didn't have a 'time' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestJSONMiddleware2xx(c *C) {
|
||||||
|
outC := make(chan string)
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, s.logReader)
|
||||||
|
fmt.Println(buf.String())
|
||||||
|
outC <- buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/api/healthy", nil)
|
||||||
|
s.logWriter.Close()
|
||||||
|
capturedOutput := <-outC
|
||||||
|
|
||||||
|
var jsonMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
|
if val, ok := jsonMap["level"]; ok {
|
||||||
|
c.Check(val, Equals, "info")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestJSONMiddleware5xx(c *C) {
|
||||||
|
outC := make(chan string)
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, s.logReader)
|
||||||
|
fmt.Println(buf.String())
|
||||||
|
outC <- buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/api/ready", nil)
|
||||||
|
s.logWriter.Close()
|
||||||
|
capturedOutput := <-outC
|
||||||
|
|
||||||
|
var jsonMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
|
if val, ok := jsonMap["level"]; ok {
|
||||||
|
c.Check(val, Equals, "error")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestJSONMiddlewareRaw(c *C) {
|
||||||
|
outC := make(chan string)
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, s.logReader)
|
||||||
|
fmt.Println(buf.String())
|
||||||
|
outC <- buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/api/healthy?test=raw", nil)
|
||||||
|
s.logWriter.Close()
|
||||||
|
capturedOutput := <-outC
|
||||||
|
|
||||||
|
var jsonMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(capturedOutput), &jsonMap)
|
||||||
|
|
||||||
|
fmt.Println(capturedOutput)
|
||||||
|
|
||||||
|
if val, ok := jsonMap["level"]; ok {
|
||||||
|
c.Check(val, Equals, "info")
|
||||||
|
} else {
|
||||||
|
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestGetBasePath(c *C) {
|
||||||
|
s.HTTPRequest(http.MethodGet, "", nil)
|
||||||
|
path := getBasePath(s.context)
|
||||||
|
c.Check(path, Equals, "/")
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/", nil)
|
||||||
|
path = getBasePath(s.context)
|
||||||
|
c.Check(path, Equals, "/")
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/api", nil)
|
||||||
|
path = getBasePath(s.context)
|
||||||
|
c.Check(path, Equals, "/api")
|
||||||
|
|
||||||
|
s.HTTPRequest(http.MethodGet, "/api/repos/testRepo", nil)
|
||||||
|
path = getBasePath(s.context)
|
||||||
|
c.Check(path, Equals, "/api/repos")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestGetURLSegment(c *C) {
|
||||||
|
url := "/"
|
||||||
|
segment, err := getURLSegment(url, 0)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
}
|
||||||
|
c.Check(*segment, Equals, "/")
|
||||||
|
|
||||||
|
_, err = getURLSegment(url, 1)
|
||||||
|
if err == nil {
|
||||||
|
c.Error("Invalid return value")
|
||||||
|
}
|
||||||
|
|
||||||
|
url = "/api"
|
||||||
|
segment, err = getURLSegment(url, 0)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
}
|
||||||
|
c.Check(*segment, Equals, "/api")
|
||||||
|
|
||||||
|
_, err = getURLSegment(url, 1)
|
||||||
|
if err == nil {
|
||||||
|
c.Error("Invalid return value")
|
||||||
|
}
|
||||||
|
|
||||||
|
url = "/api/repos/testRepo"
|
||||||
|
segment, err = getURLSegment(url, 0)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
}
|
||||||
|
c.Check(*segment, Equals, "/api")
|
||||||
|
|
||||||
|
segment, err = getURLSegment(url, 1)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
}
|
||||||
|
c.Check(*segment, Equals, "/repos")
|
||||||
|
}
|
||||||
+202
-99
@@ -2,8 +2,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -14,19 +14,16 @@ import (
|
|||||||
"github.com/aptly-dev/aptly/query"
|
"github.com/aptly-dev/aptly/query"
|
||||||
"github.com/aptly-dev/aptly/task"
|
"github.com/aptly-dev/aptly/task"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getVerifier(ignoreSignatures bool, keyRings []string) (pgp.Verifier, error) {
|
func getVerifier(keyRings []string) (pgp.Verifier, error) {
|
||||||
if ignoreSignatures {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
verifier := context.GetVerifier()
|
verifier := context.GetVerifier()
|
||||||
for _, keyRing := range keyRings {
|
for _, keyRing := range keyRings {
|
||||||
verifier.AddKeyring(keyRing)
|
verifier.AddKeyring(keyRing)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := verifier.InitKeyring()
|
err := verifier.InitKeyring(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -34,7 +31,13 @@ func getVerifier(ignoreSignatures bool, keyRings []string) (pgp.Verifier, error)
|
|||||||
return verifier, nil
|
return verifier, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/mirrors
|
// @Summary Get mirrors
|
||||||
|
// @Description **Show list of currently available mirrors**
|
||||||
|
// @Description Each mirror is returned as in “show” API.
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} deb.RemoteRepo
|
||||||
|
// @Router /api/mirrors [get]
|
||||||
func apiMirrorsList(c *gin.Context) {
|
func apiMirrorsList(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
@@ -48,24 +51,49 @@ func apiMirrorsList(c *gin.Context) {
|
|||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/mirrors
|
type mirrorCreateParams struct {
|
||||||
|
// Name of mirror to be created
|
||||||
|
Name string `binding:"required" json:"Name" example:"mirror2"`
|
||||||
|
// Url of the archive to mirror
|
||||||
|
ArchiveURL string `binding:"required" json:"ArchiveURL" example:"http://deb.debian.org/debian"`
|
||||||
|
// Distribution name to mirror
|
||||||
|
Distribution string ` json:"Distribution" example:"'buster', for flat repositories use './'"`
|
||||||
|
// Package query that is applied to mirror packages
|
||||||
|
Filter string ` json:"Filter" example:"xserver-xorg"`
|
||||||
|
// Components to mirror, if not specified aptly would fetch all components
|
||||||
|
Components []string ` json:"Components" example:"main"`
|
||||||
|
// Limit mirror to those architectures, if not specified aptly would fetch all architectures
|
||||||
|
Architectures []string ` json:"Architectures" example:"amd64"`
|
||||||
|
// Gpg keyring(s) for verifying Release file
|
||||||
|
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
|
||||||
|
// Set "true" to mirror source packages
|
||||||
|
DownloadSources bool ` json:"DownloadSources"`
|
||||||
|
// Set "true" to mirror udeb files
|
||||||
|
DownloadUdebs bool ` json:"DownloadUdebs"`
|
||||||
|
// Set "true" to mirror installer files
|
||||||
|
DownloadInstaller bool ` json:"DownloadInstaller"`
|
||||||
|
// Set "true" to include dependencies of matching packages when filtering
|
||||||
|
FilterWithDeps bool ` json:"FilterWithDeps"`
|
||||||
|
// Set "true" to skip if the given components are in the Release file
|
||||||
|
SkipComponentCheck bool ` json:"SkipComponentCheck"`
|
||||||
|
// Set "true" to skip the verification of architectures
|
||||||
|
SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"`
|
||||||
|
// Set "true" to skip the verification of Release file signatures
|
||||||
|
IgnoreSignatures bool ` json:"IgnoreSignatures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Create mirror
|
||||||
|
// @Description **Create a mirror**
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body mirrorCreateParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} deb.RemoteRepo
|
||||||
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
|
// @Router /api/mirrors [post]
|
||||||
func apiMirrorsCreate(c *gin.Context) {
|
func apiMirrorsCreate(c *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
var b struct {
|
var b mirrorCreateParams
|
||||||
Name string `binding:"required"`
|
|
||||||
ArchiveURL string `binding:"required"`
|
|
||||||
Distribution string
|
|
||||||
Filter string
|
|
||||||
Components []string
|
|
||||||
Architectures []string
|
|
||||||
Keyrings []string
|
|
||||||
DownloadSources bool
|
|
||||||
DownloadUdebs bool
|
|
||||||
DownloadInstaller bool
|
|
||||||
FilterWithDeps bool
|
|
||||||
SkipComponentCheck bool
|
|
||||||
IgnoreSignatures bool
|
|
||||||
}
|
|
||||||
|
|
||||||
b.DownloadSources = context.Config().DownloadSourcePackages
|
b.DownloadSources = context.Config().DownloadSourcePackages
|
||||||
b.IgnoreSignatures = context.Config().GpgDisableVerify
|
b.IgnoreSignatures = context.Config().GpgDisableVerify
|
||||||
@@ -81,7 +109,7 @@ func apiMirrorsCreate(c *gin.Context) {
|
|||||||
if strings.HasPrefix(b.ArchiveURL, "ppa:") {
|
if strings.HasPrefix(b.ArchiveURL, "ppa:") {
|
||||||
b.ArchiveURL, b.Distribution, b.Components, err = deb.ParsePPA(b.ArchiveURL, context.Config())
|
b.ArchiveURL, b.Distribution, b.Components, err = deb.ParsePPA(b.ArchiveURL, context.Config())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +117,7 @@ func apiMirrorsCreate(c *gin.Context) {
|
|||||||
if b.Filter != "" {
|
if b.Filter != "" {
|
||||||
_, err = query.Parse(b.Filter)
|
_, err = query.Parse(b.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to create mirror: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,39 +126,50 @@ func apiMirrorsCreate(c *gin.Context) {
|
|||||||
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller)
|
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to create mirror: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.Filter = b.Filter
|
repo.Filter = b.Filter
|
||||||
repo.FilterWithDeps = b.FilterWithDeps
|
repo.FilterWithDeps = b.FilterWithDeps
|
||||||
repo.SkipComponentCheck = b.SkipComponentCheck
|
repo.SkipComponentCheck = b.SkipComponentCheck
|
||||||
|
repo.SkipArchitectureCheck = b.SkipArchitectureCheck
|
||||||
repo.DownloadSources = b.DownloadSources
|
repo.DownloadSources = b.DownloadSources
|
||||||
repo.DownloadUdebs = b.DownloadUdebs
|
repo.DownloadUdebs = b.DownloadUdebs
|
||||||
|
|
||||||
verifier, err := getVerifier(b.IgnoreSignatures, b.Keyrings)
|
verifier, err := getVerifier(b.Keyrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
downloader := context.NewDownloader(nil)
|
downloader := context.NewDownloader(nil)
|
||||||
err = repo.Fetch(downloader, verifier)
|
err = repo.Fetch(downloader, verifier, b.IgnoreSignatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to fetch mirror: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to fetch mirror: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Add(repo)
|
err = collection.Add(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to add mirror: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to add mirror: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(201, repo)
|
c.JSON(201, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE /api/mirrors/:name
|
// @Summary Delete Mirror
|
||||||
|
// @Description **Delete a mirror**
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Param name path string true "mirror name"
|
||||||
|
// @Param force query int true "force: 1 to enable"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} task.ProcessReturnValue
|
||||||
|
// @Failure 404 {object} Error "Mirror not found"
|
||||||
|
// @Failure 403 {object} Error "Unable to delete mirror with snapshots"
|
||||||
|
// @Failure 500 {object} Error "Unable to delete"
|
||||||
|
// @Router /api/mirrors/{name} [delete]
|
||||||
func apiMirrorsDrop(c *gin.Context) {
|
func apiMirrorsDrop(c *gin.Context) {
|
||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
force := c.Request.URL.Query().Get("force") == "1"
|
force := c.Request.URL.Query().Get("force") == "1"
|
||||||
@@ -141,13 +180,13 @@ func apiMirrorsDrop(c *gin.Context) {
|
|||||||
|
|
||||||
repo, err := mirrorCollection.ByName(name)
|
repo, err := mirrorCollection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to drop: %s", err))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to drop: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
taskName := fmt.Sprintf("Delete mirror %s", name)
|
taskName := fmt.Sprintf("Delete mirror %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := repo.CheckLock()
|
err := repo.CheckLock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||||
@@ -157,7 +196,7 @@ func apiMirrorsDrop(c *gin.Context) {
|
|||||||
snapshots := snapshotCollection.ByRemoteRepoSource(repo)
|
snapshots := snapshotCollection.ByRemoteRepoSource(repo)
|
||||||
|
|
||||||
if len(snapshots) > 0 {
|
if len(snapshots) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
|
return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +208,15 @@ func apiMirrorsDrop(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/mirrors/:name
|
// @Summary Show Mirror
|
||||||
|
// @Description **Get mirror information by name**
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Param name path string true "mirror name"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} deb.RemoteRepo
|
||||||
|
// @Failure 404 {object} Error "Mirror not found"
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/mirrors/{name} [get]
|
||||||
func apiMirrorsShow(c *gin.Context) {
|
func apiMirrorsShow(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
@@ -177,19 +224,30 @@ func apiMirrorsShow(c *gin.Context) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
repo, err := collection.ByName(name)
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to show: %s", err))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to show: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to show: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, repo)
|
c.JSON(200, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/mirrors/:name/packages
|
// @Summary List Mirror Packages
|
||||||
|
// @Description **Get a list of packages from a mirror**
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Param name path string true "mirror name"
|
||||||
|
// @Param q query string false "search query"
|
||||||
|
// @Param format query string false "format: `details` for more detailed information"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} deb.Package "List of Packages"
|
||||||
|
// @Failure 400 {object} Error "Unable to determine list of architectures"
|
||||||
|
// @Failure 404 {object} Error "Mirror not found"
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/mirrors/{name}/packages [get]
|
||||||
func apiMirrorsPackages(c *gin.Context) {
|
func apiMirrorsPackages(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
@@ -197,17 +255,17 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
repo, err := collection.ByName(name)
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to show: %s", err))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to show: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to show: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.LastDownloadDate.IsZero() {
|
if repo.LastDownloadDate.IsZero() {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to show package list, mirror hasn't been downloaded yet"))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to show package list, mirror hasn't been downloaded yet"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +274,7 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
|
|
||||||
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil)
|
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +282,7 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
if queryS != "" {
|
if queryS != "" {
|
||||||
q, err := query.Parse(c.Request.URL.Query().Get("q"))
|
q, err := query.Parse(c.Request.URL.Query().Get("q"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +299,7 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
sort.Strings(architecturesList)
|
sort.Strings(architecturesList)
|
||||||
|
|
||||||
if len(architecturesList) == 0 {
|
if len(architecturesList) == 0 {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +309,7 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
|
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
|
||||||
nil, context.DependencyOptions(), architecturesList)
|
nil, context.DependencyOptions(), architecturesList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to search: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,36 +325,65 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /api/mirrors/:name
|
type mirrorUpdateParams struct {
|
||||||
|
// Change mirror name to `Name`
|
||||||
|
Name string ` json:"Name" example:"mirror1"`
|
||||||
|
// Url of the archive to mirror
|
||||||
|
ArchiveURL string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
|
||||||
|
// Package query that is applied to mirror packages
|
||||||
|
Filter string ` json:"Filter" example:"xserver-xorg"`
|
||||||
|
// Limit mirror to those architectures, if not specified aptly would fetch all architectures
|
||||||
|
Architectures []string ` json:"Architectures" example:"amd64"`
|
||||||
|
// Components to mirror, if not specified aptly would fetch all components
|
||||||
|
Components []string ` json:"Components" example:"main"`
|
||||||
|
// Gpg keyring(s) for verifing Release file
|
||||||
|
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
|
||||||
|
// Set "true" to include dependencies of matching packages when filtering
|
||||||
|
FilterWithDeps bool ` json:"FilterWithDeps"`
|
||||||
|
// Set "true" to mirror source packages
|
||||||
|
DownloadSources bool ` json:"DownloadSources"`
|
||||||
|
// Set "true" to mirror udeb files
|
||||||
|
DownloadUdebs bool ` json:"DownloadUdebs"`
|
||||||
|
// Set "true" to skip checking if the given components are in the Release file
|
||||||
|
SkipComponentCheck bool ` json:"SkipComponentCheck"`
|
||||||
|
// Set "true" to skip checking if the given architectures are in the Release file
|
||||||
|
SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"`
|
||||||
|
// Set "true" to ignore checksum errors
|
||||||
|
IgnoreChecksums bool ` json:"IgnoreChecksums"`
|
||||||
|
// Set "true" to skip the verification of Release file signatures
|
||||||
|
IgnoreSignatures bool ` json:"IgnoreSignatures"`
|
||||||
|
// Set "true" to force a mirror update even if another process is already updating the mirror (use with caution!)
|
||||||
|
ForceUpdate bool ` json:"ForceUpdate"`
|
||||||
|
// Set "true" to skip downloading already downloaded packages
|
||||||
|
SkipExistingPackages bool ` json:"SkipExistingPackages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Update Mirror
|
||||||
|
// @Description **Update Mirror and download packages**
|
||||||
|
// @Tags Mirrors
|
||||||
|
// @Param name path string true "mirror name to update"
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body mirrorUpdateParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} task.ProcessReturnValue "Mirror was updated successfully"
|
||||||
|
// @Success 202 {object} task.Task "Mirror is being updated"
|
||||||
|
// @Failure 400 {object} Error "Unable to determine list of architectures"
|
||||||
|
// @Failure 404 {object} Error "Mirror not found"
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/mirrors/{name} [put]
|
||||||
func apiMirrorsUpdate(c *gin.Context) {
|
func apiMirrorsUpdate(c *gin.Context) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
remote *deb.RemoteRepo
|
remote *deb.RemoteRepo
|
||||||
|
b mirrorUpdateParams
|
||||||
)
|
)
|
||||||
|
|
||||||
var b struct {
|
|
||||||
Name string
|
|
||||||
ArchiveURL string
|
|
||||||
Filter string
|
|
||||||
Architectures []string
|
|
||||||
Components []string
|
|
||||||
Keyrings []string
|
|
||||||
FilterWithDeps bool
|
|
||||||
DownloadSources bool
|
|
||||||
DownloadUdebs bool
|
|
||||||
SkipComponentCheck bool
|
|
||||||
IgnoreChecksums bool
|
|
||||||
IgnoreSignatures bool
|
|
||||||
ForceUpdate bool
|
|
||||||
SkipExistingPackages bool
|
|
||||||
}
|
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
|
|
||||||
remote, err = collection.ByName(c.Params.ByName("name"))
|
remote, err = collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,12 +391,14 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
b.DownloadUdebs = remote.DownloadUdebs
|
b.DownloadUdebs = remote.DownloadUdebs
|
||||||
b.DownloadSources = remote.DownloadSources
|
b.DownloadSources = remote.DownloadSources
|
||||||
b.SkipComponentCheck = remote.SkipComponentCheck
|
b.SkipComponentCheck = remote.SkipComponentCheck
|
||||||
|
b.SkipArchitectureCheck = remote.SkipArchitectureCheck
|
||||||
b.FilterWithDeps = remote.FilterWithDeps
|
b.FilterWithDeps = remote.FilterWithDeps
|
||||||
b.Filter = remote.Filter
|
b.Filter = remote.Filter
|
||||||
b.Architectures = remote.Architectures
|
b.Architectures = remote.Architectures
|
||||||
b.Components = remote.Components
|
b.Components = remote.Components
|
||||||
|
b.IgnoreSignatures = context.Config().GpgDisableVerify
|
||||||
|
|
||||||
log.Printf("%s: Starting mirror update\n", b.Name)
|
log.Info().Msgf("%s: Starting mirror update", b.Name)
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
if c.Bind(&b) != nil {
|
||||||
return
|
return
|
||||||
@@ -318,14 +407,14 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
if b.Name != remote.Name {
|
if b.Name != remote.Name {
|
||||||
_, err = collection.ByName(b.Name)
|
_, err = collection.ByName(b.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AbortWithError(409, fmt.Errorf("unable to rename: mirror %s already exists", b.Name))
|
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: mirror %s already exists", b.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.DownloadUdebs != remote.DownloadUdebs {
|
if b.DownloadUdebs != remote.DownloadUdebs {
|
||||||
if remote.IsFlat() && b.DownloadUdebs {
|
if remote.IsFlat() && b.DownloadUdebs {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to update: flat mirrors don't support udebs"))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to update: flat mirrors don't support udebs"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,14 +427,15 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
remote.DownloadUdebs = b.DownloadUdebs
|
remote.DownloadUdebs = b.DownloadUdebs
|
||||||
remote.DownloadSources = b.DownloadSources
|
remote.DownloadSources = b.DownloadSources
|
||||||
remote.SkipComponentCheck = b.SkipComponentCheck
|
remote.SkipComponentCheck = b.SkipComponentCheck
|
||||||
|
remote.SkipArchitectureCheck = b.SkipArchitectureCheck
|
||||||
remote.FilterWithDeps = b.FilterWithDeps
|
remote.FilterWithDeps = b.FilterWithDeps
|
||||||
remote.Filter = b.Filter
|
remote.Filter = b.Filter
|
||||||
remote.Architectures = b.Architectures
|
remote.Architectures = b.Architectures
|
||||||
remote.Components = b.Components
|
remote.Components = b.Components
|
||||||
|
|
||||||
verifier, err := getVerifier(b.IgnoreSignatures, b.Keyrings)
|
verifier, err := getVerifier(b.Keyrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +443,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
|
||||||
downloader := context.NewDownloader(out)
|
downloader := context.NewDownloader(out)
|
||||||
err := remote.Fetch(downloader, verifier)
|
err := remote.Fetch(downloader, verifier, b.IgnoreSignatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -365,7 +455,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.SkipComponentCheck)
|
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, b.SkipComponentCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -458,7 +548,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Printf("%s: Spawning background processes...\n", b.Name)
|
log.Info().Msgf("%s: Spawning background processes...", b.Name)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < context.Config().DownloadConcurrency; i++ {
|
for i := 0; i < context.Config().DownloadConcurrency; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@@ -476,7 +566,16 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
var e error
|
var e error
|
||||||
|
|
||||||
// provision download location
|
// provision download location
|
||||||
task.TempDownPath, e = context.PackagePool().(aptly.LocalPackagePool).GenerateTempPath(task.File.Filename)
|
if pp, ok := context.PackagePool().(aptly.LocalPackagePool); ok {
|
||||||
|
task.TempDownPath, e = pp.GenerateTempPath(task.File.Filename)
|
||||||
|
} else {
|
||||||
|
var file *os.File
|
||||||
|
file, e = os.CreateTemp("", task.File.Filename)
|
||||||
|
if e == nil {
|
||||||
|
task.TempDownPath = file.Name()
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
if e != nil {
|
if e != nil {
|
||||||
pushError(e)
|
pushError(e)
|
||||||
continue
|
continue
|
||||||
@@ -494,6 +593,20 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// and import it back to the pool
|
||||||
|
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, collectionFactory.ChecksumCollection(nil))
|
||||||
|
if err != nil {
|
||||||
|
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
|
||||||
|
pushError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// update "attached" files if any
|
||||||
|
for _, additionalAtask := range task.Additional {
|
||||||
|
additionalAtask.File.PoolPath = task.File.PoolPath
|
||||||
|
additionalAtask.File.Checksums = task.File.Checksums
|
||||||
|
}
|
||||||
|
|
||||||
task.Done = true
|
task.Done = true
|
||||||
taskFinished <- task
|
taskFinished <- task
|
||||||
case <-context.Done():
|
case <-context.Done():
|
||||||
@@ -505,32 +618,22 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all download goroutines to finish
|
// Wait for all download goroutines to finish
|
||||||
log.Printf("%s: Waiting for background processes to finish...\n", b.Name)
|
log.Info().Msgf("%s: Waiting for background processes to finish...", b.Name)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
log.Printf("%s: Background processes finished\n", b.Name)
|
log.Info().Msgf("%s: Background processes finished", b.Name)
|
||||||
close(taskFinished)
|
close(taskFinished)
|
||||||
|
|
||||||
for idx := range queue {
|
defer func() {
|
||||||
|
for _, task := range queue {
|
||||||
|
if task.TempDownPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
atask := &queue[idx]
|
if err := os.Remove(task.TempDownPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to delete %s: %v\n", task.TempDownPath, err)
|
||||||
if !atask.Done {
|
}
|
||||||
// download not finished yet
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
// and import it back to the pool
|
|
||||||
atask.File.PoolPath, err = context.PackagePool().Import(atask.TempDownPath, atask.File.Filename, &atask.File.Checksums, true, collectionFactory.ChecksumCollection(nil))
|
|
||||||
if err != nil {
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update "attached" files if any
|
|
||||||
for _, additionalAtask := range atask.Additional {
|
|
||||||
additionalAtask.File.PoolPath = atask.File.PoolPath
|
|
||||||
additionalAtask.File.Checksums = atask.File.Checksums
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-context.Done():
|
case <-context.Done():
|
||||||
@@ -539,18 +642,18 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
log.Printf("%s: Unable to update because of previous errors\n", b.Name)
|
log.Info().Msgf("%s: Unable to update because of previous errors", b.Name)
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n "))
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n "))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("%s: Finalizing download\n", b.Name)
|
log.Info().Msgf("%s: Finalizing download...", b.Name)
|
||||||
remote.FinalizeDownload(collectionFactory, out)
|
remote.FinalizeDownload(collectionFactory, out)
|
||||||
err = collectionFactory.RemoteRepoCollection().Update(remote)
|
err = collectionFactory.RemoteRepoCollection().Update(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("%s: Mirror updated successfully!\n", b.Name)
|
log.Info().Msgf("%s: Mirror updated successfully", b.Name)
|
||||||
return &task.ProcessReturnValue{Code: http.StatusNoContent, Value: nil}, nil
|
return &task.ProcessReturnValue{Code: http.StatusNoContent, Value: nil}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-1
@@ -9,9 +9,24 @@ func apiPackagesShow(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
p, err := collectionFactory.PackageCollection().ByKey([]byte(c.Params.ByName("key")))
|
p, err := collectionFactory.PackageCollection().ByKey([]byte(c.Params.ByName("key")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, p)
|
c.JSON(200, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Get packages
|
||||||
|
// @Description Get list of packages.
|
||||||
|
// @Tags Packages
|
||||||
|
// @Consume json
|
||||||
|
// @Produce json
|
||||||
|
// @Param q query string false "search query"
|
||||||
|
// @Param format query string false "format: `details` for more detailed information"
|
||||||
|
// @Success 200 {array} string "List of packages"
|
||||||
|
// @Router /api/packages [get]
|
||||||
|
func apiPackages(c *gin.Context) {
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
collection := collectionFactory.PackageCollection()
|
||||||
|
showPackages(c, collection.AllPackageRefs(), collectionFactory)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackagesSuite struct {
|
||||||
|
ApiSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&PackagesSuite{})
|
||||||
|
|
||||||
|
func (s *PackagesSuite) TestPackagesGetMaximumVersion(c *C) {
|
||||||
|
response, err := s.HTTPRequest("GET", "/api/repos/dummy/packages?maximumVersion=1", nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(response.Code, Equals, 200)
|
||||||
|
c.Check(response.Body.String(), Equals, "[]")
|
||||||
|
}
|
||||||
+48
-38
@@ -16,7 +16,6 @@ import (
|
|||||||
// SigningOptions is a shared between publish API GPG options structure
|
// SigningOptions is a shared between publish API GPG options structure
|
||||||
type SigningOptions struct {
|
type SigningOptions struct {
|
||||||
Skip bool
|
Skip bool
|
||||||
Batch bool
|
|
||||||
GpgKey string
|
GpgKey string
|
||||||
Keyring string
|
Keyring string
|
||||||
SecretKeyring string
|
SecretKeyring string
|
||||||
@@ -33,7 +32,9 @@ func getSigner(options *SigningOptions) (pgp.Signer, error) {
|
|||||||
signer.SetKey(options.GpgKey)
|
signer.SetKey(options.GpgKey)
|
||||||
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
|
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
|
||||||
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
|
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
|
||||||
signer.SetBatch(options.Batch)
|
|
||||||
|
// If Batch is false, GPG will ask for passphrase on stdin, which would block the api process
|
||||||
|
signer.SetBatch(true)
|
||||||
|
|
||||||
err := signer.Init()
|
err := signer.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -43,16 +44,23 @@ func getSigner(options *SigningOptions) (pgp.Signer, error) {
|
|||||||
return signer, nil
|
return signer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace '_' with '/' and double '__' with single '_'
|
// Replace '_' with '/' and double '__' with single '_', SanitizePath
|
||||||
func parseEscapedPath(path string) string {
|
func slashEscape(path string) string {
|
||||||
result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1)
|
result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1)
|
||||||
|
result = utils.SanitizePath(result)
|
||||||
if result == "" {
|
if result == "" {
|
||||||
result = "."
|
result = "."
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /publish
|
// @Summary Get publish points
|
||||||
|
// @Description Get list of available publish points. Each publish point is returned as in “show” API.
|
||||||
|
// @Tags Publish
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} deb.PublishedRepo
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/publish [get]
|
||||||
func apiPublishList(c *gin.Context) {
|
func apiPublishList(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
@@ -60,7 +68,7 @@ func apiPublishList(c *gin.Context) {
|
|||||||
result := make([]*deb.PublishedRepo, 0, collection.Len())
|
result := make([]*deb.PublishedRepo, 0, collection.Len())
|
||||||
|
|
||||||
err := collection.ForEach(func(repo *deb.PublishedRepo) error {
|
err := collection.ForEach(func(repo *deb.PublishedRepo) error {
|
||||||
err := collection.LoadComplete(repo, collectionFactory)
|
err := collection.LoadShallow(repo, collectionFactory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -71,7 +79,7 @@ func apiPublishList(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +88,7 @@ func apiPublishList(c *gin.Context) {
|
|||||||
|
|
||||||
// POST /publish/:prefix
|
// POST /publish/:prefix
|
||||||
func apiPublishRepoOrSnapshot(c *gin.Context) {
|
func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||||
param := parseEscapedPath(c.Params.ByName("prefix"))
|
param := slashEscape(c.Params.ByName("prefix"))
|
||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
|
|
||||||
var b struct {
|
var b struct {
|
||||||
@@ -100,20 +108,23 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
Architectures []string
|
Architectures []string
|
||||||
Signing SigningOptions
|
Signing SigningOptions
|
||||||
AcquireByHash *bool
|
AcquireByHash *bool
|
||||||
|
MultiDist bool
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
if c.Bind(&b) != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.Distribution = utils.SanitizePath(b.Distribution)
|
||||||
|
|
||||||
signer, err := getSigner(&b.Signing)
|
signer, err := getSigner(&b.Signing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b.Sources) == 0 {
|
if len(b.Sources) == 0 {
|
||||||
c.AbortWithError(400, fmt.Errorf("unable to publish: soures are empty"))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to publish: soures are empty"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +134,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
var resources []string
|
var resources []string
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
|
||||||
if b.SourceKind == "snapshot" {
|
if b.SourceKind == deb.SourceSnapshot {
|
||||||
var snapshot *deb.Snapshot
|
var snapshot *deb.Snapshot
|
||||||
|
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
@@ -134,14 +145,14 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
|
|
||||||
snapshot, err = snapshotCollection.ByName(source.Name)
|
snapshot, err = snapshotCollection.ByName(source.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to publish: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(snapshot.ResourceKey()))
|
resources = append(resources, string(snapshot.ResourceKey()))
|
||||||
err = snapshotCollection.LoadComplete(snapshot)
|
err = snapshotCollection.LoadComplete(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to publish: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,26 +169,26 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
|
|
||||||
localRepo, err = localCollection.ByName(source.Name)
|
localRepo, err = localCollection.ByName(source.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to publish: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(localRepo.Key()))
|
resources = append(resources, string(localRepo.Key()))
|
||||||
err = localCollection.LoadComplete(localRepo)
|
err = localCollection.LoadComplete(localRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to publish: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
sources = append(sources, localRepo)
|
sources = append(sources, localRepo)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithError(400, fmt.Errorf("unknown SourceKind"))
|
AbortWithJSONError(c, 400, fmt.Errorf("unknown SourceKind"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory)
|
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, b.MultiDist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to publish: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +222,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
published.SkipBz2 = context.Config().SkipBz2Publishing
|
published.SkipBz2 = context.Config().SkipBz2Publishing
|
||||||
if b.SkipContents != nil {
|
if b.SkipBz2 != nil {
|
||||||
published.SkipBz2 = *b.SkipBz2
|
published.SkipBz2 = *b.SkipBz2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,9 +252,9 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
|
|
||||||
// PUT /publish/:prefix/:distribution
|
// PUT /publish/:prefix/:distribution
|
||||||
func apiPublishUpdateSwitch(c *gin.Context) {
|
func apiPublishUpdateSwitch(c *gin.Context) {
|
||||||
param := parseEscapedPath(c.Params.ByName("prefix"))
|
param := slashEscape(c.Params.ByName("prefix"))
|
||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := c.Params.ByName("distribution")
|
distribution := utils.SanitizePath(c.Params.ByName("distribution"))
|
||||||
|
|
||||||
var b struct {
|
var b struct {
|
||||||
ForceOverwrite bool
|
ForceOverwrite bool
|
||||||
@@ -256,6 +267,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
Name string `binding:"required"`
|
Name string `binding:"required"`
|
||||||
}
|
}
|
||||||
AcquireByHash *bool
|
AcquireByHash *bool
|
||||||
|
MultiDist *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
if c.Bind(&b) != nil {
|
||||||
@@ -264,7 +276,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
|
|
||||||
signer, err := getSigner(&b.Signing)
|
signer, err := getSigner(&b.Signing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,12 +285,12 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
|
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, 404, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
err = collection.LoadComplete(published, collectionFactory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +300,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
|
|
||||||
if published.SourceKind == deb.SourceLocalRepo {
|
if published.SourceKind == deb.SourceLocalRepo {
|
||||||
if len(b.Snapshots) > 0 {
|
if len(b.Snapshots) > 0 {
|
||||||
c.AbortWithError(400, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
AbortWithJSONError(c, 400, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updatedComponents = published.Components()
|
updatedComponents = published.Components()
|
||||||
@@ -296,23 +308,17 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
published.UpdateLocalRepo(component)
|
published.UpdateLocalRepo(component)
|
||||||
}
|
}
|
||||||
} else if published.SourceKind == "snapshot" {
|
} else if published.SourceKind == "snapshot" {
|
||||||
publishedComponents := published.Components()
|
|
||||||
for _, snapshotInfo := range b.Snapshots {
|
for _, snapshotInfo := range b.Snapshots {
|
||||||
if !utils.StrSliceHasItem(publishedComponents, snapshotInfo.Component) {
|
|
||||||
c.AbortWithError(404, fmt.Errorf("component %s is not in published repository", snapshotInfo.Component))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
c.AbortWithError(404, err2)
|
AbortWithJSONError(c, 404, err2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err2 = snapshotCollection.LoadComplete(snapshot)
|
err2 = snapshotCollection.LoadComplete(snapshot)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
c.AbortWithError(500, err2)
|
AbortWithJSONError(c, 500, err2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +327,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
updatedSnapshots = append(updatedSnapshots, snapshot.Name)
|
updatedSnapshots = append(updatedSnapshots, snapshot.Name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithError(500, fmt.Errorf("unknown published repository type"))
|
AbortWithJSONError(c, 500, fmt.Errorf("unknown published repository type"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,9 +343,13 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
published.AcquireByHash = *b.AcquireByHash
|
published.AcquireByHash = *b.AcquireByHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.MultiDist != nil {
|
||||||
|
published.MultiDist = *b.MultiDist
|
||||||
|
}
|
||||||
|
|
||||||
resources = append(resources, string(published.Key()))
|
resources = append(resources, string(published.Key()))
|
||||||
taskName := fmt.Sprintf("Update published %s (%s): %s", published.SourceKind, strings.Join(updatedComponents, " "), strings.Join(updatedSnapshots, ", "))
|
taskName := fmt.Sprintf("Update published %s (%s): %s", published.SourceKind, strings.Join(updatedComponents, " "), strings.Join(updatedSnapshots, ", "))
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite)
|
err := published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
@@ -367,7 +377,7 @@ func apiPublishDrop(c *gin.Context) {
|
|||||||
force := c.Request.URL.Query().Get("force") == "1"
|
force := c.Request.URL.Query().Get("force") == "1"
|
||||||
skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1"
|
skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1"
|
||||||
|
|
||||||
param := parseEscapedPath(c.Params.ByName("prefix"))
|
param := slashEscape(c.Params.ByName("prefix"))
|
||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := c.Params.ByName("distribution")
|
distribution := c.Params.ByName("distribution")
|
||||||
|
|
||||||
@@ -376,14 +386,14 @@ func apiPublishDrop(c *gin.Context) {
|
|||||||
|
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("unable to drop: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to drop: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
|
|
||||||
taskName := fmt.Sprintf("Delete published %s (%s)", prefix, distribution)
|
taskName := fmt.Sprintf("Delete published %s (%s)", prefix, distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := collection.Remove(context, storage, prefix, distribution,
|
err := collection.Remove(context, storage, prefix, distribution,
|
||||||
collectionFactory, out, force, skipCleanup)
|
collectionFactory, out, force, skipCleanup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+243
-27
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -17,7 +18,44 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/repos
|
// GET /repos
|
||||||
|
func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
c.Writer.Flush()
|
||||||
|
c.Writer.WriteString("<pre>\n")
|
||||||
|
if len(localRepos) == 0 {
|
||||||
|
c.Writer.WriteString("<a href=\"-/\">default</a>\n")
|
||||||
|
}
|
||||||
|
for publishPrefix := range localRepos {
|
||||||
|
c.Writer.WriteString(fmt.Sprintf("<a href=\"%[1]s/\">%[1]s</a>\n", publishPrefix))
|
||||||
|
}
|
||||||
|
c.Writer.WriteString("</pre>")
|
||||||
|
c.Writer.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /repos/:storage/*pkgPath
|
||||||
|
func reposServeInAPIMode(c *gin.Context) {
|
||||||
|
pkgpath := c.Param("pkgPath")
|
||||||
|
|
||||||
|
storage := c.Param("storage")
|
||||||
|
if storage == "-" {
|
||||||
|
storage = ""
|
||||||
|
} else {
|
||||||
|
storage = "filesystem:" + storage
|
||||||
|
}
|
||||||
|
|
||||||
|
publicPath := context.GetPublishedStorage(storage).(aptly.FileSystemPublishedStorage).PublicPath()
|
||||||
|
c.FileFromFS(pkgpath, http.Dir(publicPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Get repos
|
||||||
|
// @Description Get list of available repos. Each repo is returned as in “show” API.
|
||||||
|
// @Tags Repos
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} deb.LocalRepo
|
||||||
|
// @Router /api/repos [get]
|
||||||
func apiReposList(c *gin.Context) {
|
func apiReposList(c *gin.Context) {
|
||||||
result := []*deb.LocalRepo{}
|
result := []*deb.LocalRepo{}
|
||||||
|
|
||||||
@@ -31,7 +69,18 @@ func apiReposList(c *gin.Context) {
|
|||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/repos
|
// @Summary Create repository
|
||||||
|
// @Description Create a local repository.
|
||||||
|
// @Tags Repos
|
||||||
|
// @Produce json
|
||||||
|
// @Consume json
|
||||||
|
// @Param Name query string false "Name of repository to be created."
|
||||||
|
// @Param Comment query string false "Text describing local repository, for the user"
|
||||||
|
// @Param DefaultDistribution query string false "Default distribution when publishing from this local repo"
|
||||||
|
// @Param DefaultComponent query string false "Default component when publishing from this local repo"
|
||||||
|
// @Success 201 {object} deb.LocalRepo
|
||||||
|
// @Failure 400 {object} Error "Repository already exists"
|
||||||
|
// @Router /api/repos [post]
|
||||||
func apiReposCreate(c *gin.Context) {
|
func apiReposCreate(c *gin.Context) {
|
||||||
var b struct {
|
var b struct {
|
||||||
Name string `binding:"required"`
|
Name string `binding:"required"`
|
||||||
@@ -52,7 +101,7 @@ func apiReposCreate(c *gin.Context) {
|
|||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
err := collection.Add(repo)
|
err := collection.Add(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +126,7 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
|
|
||||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
repo, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +134,7 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
_, err := collection.ByName(*b.Name)
|
_, err := collection.ByName(*b.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// already exists
|
// already exists
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repo.Name = *b.Name
|
repo.Name = *b.Name
|
||||||
@@ -102,7 +151,7 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
|
|
||||||
err = collection.Update(repo)
|
err = collection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,13 +159,21 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/repos/:name
|
// GET /api/repos/:name
|
||||||
|
// @Summary Get repository info by name
|
||||||
|
// @Description Returns basic information about local repository.
|
||||||
|
// @Tags Repos
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Repository name"
|
||||||
|
// @Success 200 {object} deb.LocalRepo
|
||||||
|
// @Failure 404 {object} Error "Repository not found"
|
||||||
|
// @Router /api/repos/{name} [get]
|
||||||
func apiReposShow(c *gin.Context) {
|
func apiReposShow(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
repo, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,13 +192,13 @@ func apiReposDrop(c *gin.Context) {
|
|||||||
|
|
||||||
repo, err := collection.ByName(name)
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
taskName := fmt.Sprintf("Delete repo %s", name)
|
taskName := fmt.Sprintf("Delete repo %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
published := publishedCollection.ByLocalRepo(repo)
|
published := publishedCollection.ByLocalRepo(repo)
|
||||||
if len(published) > 0 {
|
if len(published) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published")
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published")
|
||||||
@@ -165,13 +222,13 @@ func apiReposPackagesShow(c *gin.Context) {
|
|||||||
|
|
||||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
repo, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,18 +250,18 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
|||||||
|
|
||||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
repo, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
out.Printf("Loading packages...\n")
|
out.Printf("Loading packages...\n")
|
||||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -262,7 +319,22 @@ func apiReposPackageFromFile(c *gin.Context) {
|
|||||||
apiReposPackageFromDir(c)
|
apiReposPackageFromDir(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /repos/:name/file/:dir
|
// @Summary Add packages from uploaded file/directory
|
||||||
|
// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically.
|
||||||
|
// @Description Adding same package to local repository is not an error.
|
||||||
|
// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import).
|
||||||
|
// @Tags Repos
|
||||||
|
// @Param name path string true "Repository name"
|
||||||
|
// @Param dir path string true "Directory to add"
|
||||||
|
// @Consume json
|
||||||
|
// @Param noRemove query string false "when value is set to 1, don’t remove any files"
|
||||||
|
// @Param forceReplace query string false "when value is set to 1, remove packages conflicting with package being added (in local repository)"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {string} string "OK"
|
||||||
|
// @Failure 400 {object} Error "wrong file"
|
||||||
|
// @Failure 404 {object} Error "Repository not found"
|
||||||
|
// @Failure 500 {object} Error "Error adding files"
|
||||||
|
// @Router /api/repos/{name}/{dir} [post]
|
||||||
func apiReposPackageFromDir(c *gin.Context) {
|
func apiReposPackageFromDir(c *gin.Context) {
|
||||||
forceReplace := c.Request.URL.Query().Get("forceReplace") == "1"
|
forceReplace := c.Request.URL.Query().Get("forceReplace") == "1"
|
||||||
noRemove := c.Request.URL.Query().Get("noRemove") == "1"
|
noRemove := c.Request.URL.Query().Get("noRemove") == "1"
|
||||||
@@ -271,10 +343,10 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dirParam := c.Params.ByName("dir")
|
dirParam := utils.SanitizePath(c.Params.ByName("dir"))
|
||||||
fileParam := c.Params.ByName("file")
|
fileParam := utils.SanitizePath(c.Params.ByName("file"))
|
||||||
if fileParam != "" && !verifyPath(fileParam) {
|
if fileParam != "" && !verifyPath(fileParam) {
|
||||||
c.AbortWithError(400, fmt.Errorf("wrong file"))
|
AbortWithJSONError(c, 400, fmt.Errorf("wrong file"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,13 +356,13 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
repo, err := collection.ByName(name)
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +378,7 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
|
|
||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
resources = append(resources, sources...)
|
resources = append(resources, sources...)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
verifier := context.GetVerifier()
|
verifier := context.GetVerifier()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -382,6 +454,150 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /repos/:name/copy/:src/:file
|
||||||
|
func apiReposCopyPackage(c *gin.Context) {
|
||||||
|
dstRepoName := c.Params.ByName("name")
|
||||||
|
srcRepoName := c.Params.ByName("src")
|
||||||
|
fileName := c.Params.ByName("file")
|
||||||
|
|
||||||
|
jsonBody := struct {
|
||||||
|
WithDeps bool `json:"with-deps,omitempty"`
|
||||||
|
DryRun bool `json:"dry-run,omitempty"`
|
||||||
|
}{
|
||||||
|
WithDeps: false,
|
||||||
|
DryRun: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Bind(&jsonBody)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
srcRefList *deb.PackageRefList
|
||||||
|
srcRepo *deb.LocalRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
srcRepo, err = collectionFactory.LocalRepoCollection().ByName(srcRepoName)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcRepo.UUID == dstRepo.UUID {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest and source are identical"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srcRefList = srcRepo.RefList()
|
||||||
|
taskName := fmt.Sprintf("Copy packages from repo %s to repo %s", srcRepoName, dstRepoName)
|
||||||
|
resources := []string{string(dstRepo.Key()), string(srcRepo.Key())}
|
||||||
|
|
||||||
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
reporter := &aptly.RecordingResultReporter{
|
||||||
|
Warnings: []string{},
|
||||||
|
AddedLines: []string{},
|
||||||
|
RemovedLines: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
srcList, err := deb.NewPackageListFromRefList(srcRefList, collectionFactory.PackageCollection(), context.Progress())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcList.PrepareIndex()
|
||||||
|
|
||||||
|
var architecturesList []string
|
||||||
|
|
||||||
|
if jsonBody.WithDeps {
|
||||||
|
dstList.PrepareIndex()
|
||||||
|
|
||||||
|
// Calculate architectures
|
||||||
|
if len(context.ArchitecturesList()) > 0 {
|
||||||
|
architecturesList = context.ArchitecturesList()
|
||||||
|
} else {
|
||||||
|
architecturesList = dstList.Architectures(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(architecturesList)
|
||||||
|
|
||||||
|
if len(architecturesList) == 0 {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// srcList.Filter|FilterWithProgress only accept query list
|
||||||
|
queries := make([]deb.PackageQuery, 1)
|
||||||
|
queries[0], err = query.Parse(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to parse query '%s': %s", fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toProcess, err := srcList.FilterWithProgress(queries, jsonBody.WithDeps, dstList, context.DependencyOptions(), architecturesList, context.Progress())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("filter error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if toProcess.Len() == 0 {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("no package found for filter: '%s'", fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toProcess.ForEach(func(p *deb.Package) error {
|
||||||
|
err = dstList.Add(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("added %s-%s(%s)", p.Name, p.Version, p.Architecture)
|
||||||
|
reporter.AddedLines = append(reporter.AddedLines, name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("error processing dest add: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonBody.DryRun {
|
||||||
|
reporter.Warning("Changes not saved, as dry run has been requested")
|
||||||
|
} else {
|
||||||
|
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
|
||||||
|
|
||||||
|
err = collectionFactory.LocalRepoCollection().Update(dstRepo)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{
|
||||||
|
"Report": reporter,
|
||||||
|
}}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// POST /repos/:name/include/:dir/:file
|
// POST /repos/:name/include/:dir/:file
|
||||||
func apiReposIncludePackageFromFile(c *gin.Context) {
|
func apiReposIncludePackageFromFile(c *gin.Context) {
|
||||||
// redirect all work to dir method
|
// redirect all work to dir method
|
||||||
@@ -404,10 +620,10 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
|
|
||||||
var sources []string
|
var sources []string
|
||||||
var taskName string
|
var taskName string
|
||||||
dirParam := c.Params.ByName("dir")
|
dirParam := utils.SanitizePath(c.Params.ByName("dir"))
|
||||||
fileParam := c.Params.ByName("file")
|
fileParam := utils.SanitizePath(c.Params.ByName("file"))
|
||||||
if fileParam != "" && !verifyPath(fileParam) {
|
if fileParam != "" && !verifyPath(fileParam) {
|
||||||
c.AbortWithError(400, fmt.Errorf("wrong file"))
|
AbortWithJSONError(c, 400, fmt.Errorf("wrong file"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +637,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
|
|
||||||
repoTemplate, err := template.New("repo").Parse(repoTemplateString)
|
repoTemplate, err := template.New("repo").Parse(repoTemplateString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, fmt.Errorf("error parsing repo template: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("error parsing repo template: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +648,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
// repo template string is simple text so only use resource key of specific repository
|
// repo template string is simple text so only use resource key of specific repository
|
||||||
repo, err := collectionFactory.LocalRepoCollection().ByName(repoTemplateString)
|
repo, err := collectionFactory.LocalRepoCollection().ByName(repoTemplateString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,7 +656,7 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
resources = append(resources, sources...)
|
resources = append(resources, sources...)
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
verifier = context.GetVerifier()
|
verifier = context.GetVerifier()
|
||||||
|
|||||||
+140
-70
@@ -2,36 +2,92 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
ctx "github.com/aptly-dev/aptly/context"
|
ctx "github.com/aptly-dev/aptly/context"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
_ "github.com/aptly-dev/aptly/docs" // import docs
|
||||||
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var context *ctx.AptlyContext
|
var context *ctx.AptlyContext
|
||||||
|
|
||||||
func apiMetricsGet() gin.HandlerFunc {
|
func apiMetricsGet() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
countPackagesByRepos()
|
||||||
promhttp.Handler().ServeHTTP(c.Writer, c.Request)
|
promhttp.Handler().ServeHTTP(c.Writer, c.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func redirectSwagger(c *gin.Context) {
|
||||||
|
if c.Request.URL.Path == "/docs/" {
|
||||||
|
c.Redirect(http.StatusMovedPermanently, "/docs/index.html")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
// Router returns prebuilt with routes http.Handler
|
// Router returns prebuilt with routes http.Handler
|
||||||
|
// @title Aptly API
|
||||||
|
// @version 1.0
|
||||||
|
// @description Aptly REST API Documentation
|
||||||
|
|
||||||
|
// @contact.name Aptly
|
||||||
|
// @contact.url http://github.com/aptly-dev/aptly
|
||||||
|
// @contact.email support@aptly.info
|
||||||
|
|
||||||
|
// @license.name MIT License
|
||||||
|
// @license.url http://www.
|
||||||
|
|
||||||
|
// @BasePath /api
|
||||||
func Router(c *ctx.AptlyContext) http.Handler {
|
func Router(c *ctx.AptlyContext) http.Handler {
|
||||||
context = c
|
if aptly.EnableDebug {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
router := gin.Default()
|
} else {
|
||||||
router.UseRawPath = true
|
gin.SetMode(gin.ReleaseMode)
|
||||||
router.Use(gin.ErrorLogger())
|
|
||||||
|
|
||||||
if c.Config().EnableMetricsEndpoint {
|
|
||||||
router.Use(instrumentHandlerInFlight(apiRequestsInFlightGauge, getBasePath))
|
|
||||||
router.Use(instrumentHandlerCounter(apiRequestsTotalCounter, getBasePath))
|
|
||||||
router.Use(instrumentHandlerRequestSize(apiRequestSizeSummary, getBasePath))
|
|
||||||
router.Use(instrumentHandlerResponseSize(apiResponseSizeSummary, getBasePath))
|
|
||||||
router.Use(instrumentHandlerDuration(apiRequestsDurationSummary, getBasePath))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
context = c
|
||||||
|
|
||||||
|
router.UseRawPath = true
|
||||||
|
|
||||||
|
if c.Config().LogFormat == "json" {
|
||||||
|
c.StructuredLogging(true)
|
||||||
|
utils.SetupJSONLogger(c.Config().LogLevel, os.Stdout)
|
||||||
|
gin.DefaultWriter = utils.LogWriter{Logger: log.Logger}
|
||||||
|
router.Use(JSONLogger())
|
||||||
|
} else {
|
||||||
|
c.StructuredLogging(false)
|
||||||
|
utils.SetupDefaultLogger(c.Config().LogLevel)
|
||||||
|
router.Use(gin.Logger())
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Use(gin.Recovery(), gin.ErrorLogger())
|
||||||
|
|
||||||
|
if c.Config().EnableSwaggerEndpoint {
|
||||||
|
router.Use(redirectSwagger)
|
||||||
|
url := ginSwagger.URL("/docs/doc.json")
|
||||||
|
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Config().EnableMetricsEndpoint {
|
||||||
|
MetricsCollectorRegistrar.Register(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Config().ServeInAPIMode {
|
||||||
|
router.GET("/repos/", reposListInAPIMode(c.Config().FileSystemPublishRoots))
|
||||||
|
router.GET("/repos/:storage/*pkgPath", reposServeInAPIMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
api := router.Group("/api")
|
||||||
if context.Flags().Lookup("no-lock").Value.Get().(bool) {
|
if context.Flags().Lookup("no-lock").Value.Get().(bool) {
|
||||||
// We use a goroutine to count the number of
|
// We use a goroutine to count the number of
|
||||||
// concurrent requests. When no more requests are
|
// concurrent requests. When no more requests are
|
||||||
@@ -40,7 +96,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
|
|
||||||
go acquireDatabase()
|
go acquireDatabase()
|
||||||
|
|
||||||
router.Use(func(c *gin.Context) {
|
api.Use(func(c *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
@@ -48,7 +104,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
|
|
||||||
err = <-errCh
|
err = <-errCh
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +112,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
dbRequests <- dbRequest{releasedb, errCh}
|
dbRequests <- dbRequest{releasedb, errCh}
|
||||||
err = <-errCh
|
err = <-errCh
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -64,99 +120,113 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
root := router.Group("/api")
|
|
||||||
|
|
||||||
{
|
{
|
||||||
if c.Config().EnableMetricsEndpoint {
|
if c.Config().EnableMetricsEndpoint {
|
||||||
root.GET("/metrics", apiMetricsGet())
|
api.GET("/metrics", apiMetricsGet())
|
||||||
}
|
}
|
||||||
root.GET("/version", apiVersion)
|
api.GET("/version", apiVersion)
|
||||||
|
api.GET("/storage", apiDiskFree)
|
||||||
|
|
||||||
|
isReady := &atomic.Value{}
|
||||||
|
isReady.Store(false)
|
||||||
|
defer isReady.Store(true)
|
||||||
|
api.GET("/ready", apiReady(isReady))
|
||||||
|
api.GET("/healthy", apiHealthy)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/repos", apiReposList)
|
api.GET("/repos", apiReposList)
|
||||||
root.POST("/repos", apiReposCreate)
|
api.POST("/repos", apiReposCreate)
|
||||||
root.GET("/repos/:name", apiReposShow)
|
api.GET("/repos/:name", apiReposShow)
|
||||||
root.PUT("/repos/:name", apiReposEdit)
|
api.PUT("/repos/:name", apiReposEdit)
|
||||||
root.DELETE("/repos/:name", apiReposDrop)
|
api.DELETE("/repos/:name", apiReposDrop)
|
||||||
|
|
||||||
root.GET("/repos/:name/packages", apiReposPackagesShow)
|
api.GET("/repos/:name/packages", apiReposPackagesShow)
|
||||||
root.POST("/repos/:name/packages", apiReposPackagesAdd)
|
api.POST("/repos/:name/packages", apiReposPackagesAdd)
|
||||||
root.DELETE("/repos/:name/packages", apiReposPackagesDelete)
|
api.DELETE("/repos/:name/packages", apiReposPackagesDelete)
|
||||||
|
|
||||||
root.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
|
api.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
|
||||||
root.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
|
api.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
|
||||||
|
api.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage)
|
||||||
|
|
||||||
root.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
|
api.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
|
||||||
root.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)
|
api.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)
|
||||||
|
|
||||||
root.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
|
api.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
|
api.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/mirrors", apiMirrorsList)
|
api.GET("/mirrors", apiMirrorsList)
|
||||||
root.GET("/mirrors/:name", apiMirrorsShow)
|
api.GET("/mirrors/:name", apiMirrorsShow)
|
||||||
root.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
||||||
root.POST("/mirrors", apiMirrorsCreate)
|
api.POST("/mirrors", apiMirrorsCreate)
|
||||||
root.PUT("/mirrors/:name", apiMirrorsUpdate)
|
api.PUT("/mirrors/:name", apiMirrorsUpdate)
|
||||||
root.DELETE("/mirrors/:name", apiMirrorsDrop)
|
api.DELETE("/mirrors/:name", apiMirrorsDrop)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.POST("/gpg/key", apiGPGAddKey)
|
api.POST("/gpg/key", apiGPGAddKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/files", apiFilesListDirs)
|
api.GET("/s3", apiS3List)
|
||||||
root.POST("/files/:dir", apiFilesUpload)
|
|
||||||
root.GET("/files/:dir", apiFilesListFiles)
|
|
||||||
root.DELETE("/files/:dir", apiFilesDeleteDir)
|
|
||||||
root.DELETE("/files/:dir/:name", apiFilesDeleteFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/publish", apiPublishList)
|
api.GET("/files", apiFilesListDirs)
|
||||||
root.POST("/publish", apiPublishRepoOrSnapshot)
|
api.POST("/files/:dir", apiFilesUpload)
|
||||||
root.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
|
api.GET("/files/:dir", apiFilesListFiles)
|
||||||
root.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
|
api.DELETE("/files/:dir", apiFilesDeleteDir)
|
||||||
root.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
|
api.DELETE("/files/:dir/:name", apiFilesDeleteFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/snapshots", apiSnapshotsList)
|
|
||||||
root.POST("/snapshots", apiSnapshotsCreate)
|
api.GET("/publish", apiPublishList)
|
||||||
root.PUT("/snapshots/:name", apiSnapshotsUpdate)
|
api.POST("/publish", apiPublishRepoOrSnapshot)
|
||||||
root.GET("/snapshots/:name", apiSnapshotsShow)
|
api.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
|
||||||
root.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
|
api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
|
||||||
root.DELETE("/snapshots/:name", apiSnapshotsDrop)
|
api.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
|
||||||
root.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/packages/:key", apiPackagesShow)
|
api.GET("/snapshots", apiSnapshotsList)
|
||||||
|
api.POST("/snapshots", apiSnapshotsCreate)
|
||||||
|
api.PUT("/snapshots/:name", apiSnapshotsUpdate)
|
||||||
|
api.GET("/snapshots/:name", apiSnapshotsShow)
|
||||||
|
api.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
|
||||||
|
api.DELETE("/snapshots/:name", apiSnapshotsDrop)
|
||||||
|
api.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
|
||||||
|
api.POST("/snapshots/:name/merge", apiSnapshotsMerge)
|
||||||
|
api.POST("/snapshots/:name/pull", apiSnapshotsPull)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
root.GET("/graph.:ext", apiGraph)
|
api.GET("/packages/:key", apiPackagesShow)
|
||||||
|
api.GET("/packages", apiPackages)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
api.GET("/graph.:ext", apiGraph)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
root.POST("/db/cleanup", apiDbCleanup)
|
api.POST("/db/cleanup", apiDbCleanup)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
root.GET("/tasks", apiTasksList)
|
api.GET("/tasks", apiTasksList)
|
||||||
root.POST("/tasks-clear", apiTasksClear)
|
api.POST("/tasks-clear", apiTasksClear)
|
||||||
root.GET("/tasks-wait", apiTasksWait)
|
api.GET("/tasks-wait", apiTasksWait)
|
||||||
root.GET("/tasks/:id/wait", apiTasksWaitForTaskByID)
|
api.GET("/tasks/:id/wait", apiTasksWaitForTaskByID)
|
||||||
root.GET("/tasks/:id/output", apiTasksOutputShow)
|
api.GET("/tasks/:id/output", apiTasksOutputShow)
|
||||||
root.GET("/tasks/:id/detail", apiTasksDetailShow)
|
api.GET("/tasks/:id/detail", apiTasksDetailShow)
|
||||||
root.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
|
api.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
|
||||||
root.GET("/tasks/:id", apiTasksShow)
|
api.GET("/tasks/:id", apiTasksShow)
|
||||||
root.DELETE("/tasks/:id", apiTasksDelete)
|
api.DELETE("/tasks/:id", apiTasksDelete)
|
||||||
root.POST("/tasks-dummy", apiTasksDummy)
|
api.POST("/tasks-dummy", apiTasksDummy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return router
|
return router
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Summary Get S3 buckets
|
||||||
|
// @Description Get list of S3 buckets.
|
||||||
|
// @Tags S3
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} string "List of S3 buckets"
|
||||||
|
// @Router /api/s3 [get]
|
||||||
|
func apiS3List(c *gin.Context) {
|
||||||
|
keys := []string{}
|
||||||
|
for k := range context.Config().S3PublishRoots {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
c.JSON(200, keys)
|
||||||
|
}
|
||||||
+318
-22
@@ -3,15 +3,23 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
"github.com/aptly-dev/aptly/deb"
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
"github.com/aptly-dev/aptly/query"
|
||||||
"github.com/aptly-dev/aptly/task"
|
"github.com/aptly-dev/aptly/task"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/snapshots
|
// @Summary Get snapshots
|
||||||
|
// @Description Get list of available snapshots. Each snapshot is returned as in “show” API.
|
||||||
|
// @Tags Snapshots
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} deb.Snapshot
|
||||||
|
// @Router /api/snapshots [get]
|
||||||
func apiSnapshotsList(c *gin.Context) {
|
func apiSnapshotsList(c *gin.Context) {
|
||||||
SortMethodString := c.Request.URL.Query().Get("sort")
|
SortMethodString := c.Request.URL.Query().Get("sort")
|
||||||
|
|
||||||
@@ -55,14 +63,14 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
|
|||||||
|
|
||||||
repo, err = collection.ByName(name)
|
repo, err = collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// including snapshot resource key
|
// including snapshot resource key
|
||||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
resources := []string{string(repo.Key()), "S" + b.Name}
|
||||||
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
|
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := repo.CheckLock()
|
err := repo.CheckLock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
|
||||||
@@ -123,20 +131,20 @@ func apiSnapshotsCreate(c *gin.Context) {
|
|||||||
for i := range b.SourceSnapshots {
|
for i := range b.SourceSnapshots {
|
||||||
sources[i], err = snapshotCollection.ByName(b.SourceSnapshots[i])
|
sources[i], err = snapshotCollection.ByName(b.SourceSnapshots[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snapshotCollection.LoadComplete(sources[i])
|
err = snapshotCollection.LoadComplete(sources[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(sources[i].ResourceKey()))
|
resources = append(resources, string(sources[i].ResourceKey()))
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
list := deb.NewPackageList()
|
list := deb.NewPackageList()
|
||||||
|
|
||||||
// verify package refs and build package list
|
// verify package refs and build package list
|
||||||
@@ -160,7 +168,7 @@ func apiSnapshotsCreate(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||||
}
|
}
|
||||||
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: nil}, nil
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,14 +196,14 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
|
|||||||
|
|
||||||
repo, err = collection.ByName(name)
|
repo, err = collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// including snapshot resource key
|
// including snapshot resource key
|
||||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
resources := []string{string(repo.Key()), "S" + b.Name}
|
||||||
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
|
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := collection.LoadComplete(repo)
|
err := collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
@@ -240,13 +248,13 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
|||||||
|
|
||||||
snapshot, err = collection.ByName(name)
|
snapshot, err = collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(snapshot.ResourceKey()), "S" + b.Name}
|
resources := []string{string(snapshot.ResourceKey()), "S" + b.Name}
|
||||||
taskName := fmt.Sprintf("Update snapshot %s", name)
|
taskName := fmt.Sprintf("Update snapshot %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
_, err := collection.ByName(b.Name)
|
_, err := collection.ByName(b.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
|
||||||
@@ -275,13 +283,13 @@ func apiSnapshotsShow(c *gin.Context) {
|
|||||||
|
|
||||||
snapshot, err := collection.ByName(c.Params.ByName("name"))
|
snapshot, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(snapshot)
|
err = collection.LoadComplete(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,13 +307,13 @@ func apiSnapshotsDrop(c *gin.Context) {
|
|||||||
|
|
||||||
snapshot, err := snapshotCollection.ByName(name)
|
snapshot, err := snapshotCollection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(snapshot.ResourceKey())}
|
resources := []string{string(snapshot.ResourceKey())}
|
||||||
taskName := fmt.Sprintf("Delete snapshot %s", name)
|
taskName := fmt.Sprintf("Delete snapshot %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
published := publishedCollection.BySnapshot(snapshot)
|
published := publishedCollection.BySnapshot(snapshot)
|
||||||
|
|
||||||
if len(published) > 0 {
|
if len(published) > 0 {
|
||||||
@@ -336,32 +344,32 @@ func apiSnapshotsDiff(c *gin.Context) {
|
|||||||
|
|
||||||
snapshotA, err := collection.ByName(c.Params.ByName("name"))
|
snapshotA, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot"))
|
snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(snapshotA)
|
err = collection.LoadComplete(snapshotA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(snapshotB)
|
err = collection.LoadComplete(snapshotB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate diff
|
// Calculate diff
|
||||||
diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), collectionFactory.PackageCollection())
|
diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), collectionFactory.PackageCollection())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,15 +393,303 @@ func apiSnapshotsSearchPackages(c *gin.Context) {
|
|||||||
|
|
||||||
snapshot, err := collection.ByName(c.Params.ByName("name"))
|
snapshot, err := collection.ByName(c.Params.ByName("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(snapshot)
|
err = collection.LoadComplete(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showPackages(c, snapshot.RefList(), collectionFactory)
|
showPackages(c, snapshot.RefList(), collectionFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type snapshotsMergeParams struct {
|
||||||
|
// List of snapshot names to be merged
|
||||||
|
Sources []string `binding:"required" json:"Sources" example:"snapshot1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Snapshot Merge
|
||||||
|
// @Description **Merge several source snapshots into a new snapshot**
|
||||||
|
// @Description
|
||||||
|
// @Description Merge happens from left to right. By default, packages with the same name-architecture pair are replaced during merge (package from latest snapshot on the list wins).
|
||||||
|
// @Description
|
||||||
|
// @Description If only one snapshot is specified, merge copies source into destination.
|
||||||
|
// @Tags Snapshots
|
||||||
|
// @Param name path string true "Name of the snapshot to be created"
|
||||||
|
// @Param latest query int false "merge only the latest version of each package"
|
||||||
|
// @Param no-remove query int false "all versions of packages are preserved during merge"
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body snapshotsMergeParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200
|
||||||
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
|
// @Failure 404 {object} Error "Not Found"
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/snapshots/{name}/merge [post]
|
||||||
|
func apiSnapshotsMerge(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
snapshot *deb.Snapshot
|
||||||
|
body snapshotsMergeParams
|
||||||
|
)
|
||||||
|
|
||||||
|
name := c.Params.ByName("name")
|
||||||
|
|
||||||
|
if c.Bind(&body) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body.Sources) < 1 {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("At least one source snapshot is required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
latest := c.Request.URL.Query().Get("latest") == "1"
|
||||||
|
noRemove := c.Request.URL.Query().Get("no-remove") == "1"
|
||||||
|
overrideMatching := !latest && !noRemove
|
||||||
|
|
||||||
|
if noRemove && latest {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("no-remove and latest are mutually exclusive"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
|
sources := make([]*deb.Snapshot, len(body.Sources))
|
||||||
|
resources := make([]string, len(sources))
|
||||||
|
for i := range body.Sources {
|
||||||
|
sources[i], err = snapshotCollection.ByName(body.Sources[i])
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = snapshotCollection.LoadComplete(sources[i])
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources[i] = string(sources[i].ResourceKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
result := sources[0].RefList()
|
||||||
|
for i := 1; i < len(sources); i++ {
|
||||||
|
result = result.Merge(sources[i].RefList(), overrideMatching, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if latest {
|
||||||
|
result.FilterLatestRefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceDescription := make([]string, len(sources))
|
||||||
|
for i, s := range sources {
|
||||||
|
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot = deb.NewSnapshotFromRefList(name, sources, result,
|
||||||
|
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
|
||||||
|
|
||||||
|
err = collectionFactory.SnapshotCollection().Add(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type snapshotsPullParams struct {
|
||||||
|
// Source name to be searched for packages and dependencies
|
||||||
|
Source string `binding:"required" json:"Source" example:"source-snapshot"`
|
||||||
|
// Name of the snapshot to be created
|
||||||
|
Destination string `binding:"required" json:"Destination" example:"idestination-snapshot"`
|
||||||
|
// List of package queries (i.e. name of package to be pulled from `Source`)
|
||||||
|
Queries []string `binding:"required" json:"Queries" example:"xserver-xorg"`
|
||||||
|
// List of architectures (optional)
|
||||||
|
Architectures []string ` json:"Architectures" example:"amd64, armhf"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Snapshot Pull
|
||||||
|
// @Description **Pulls new packages and dependencies from a source snapshot into a new snapshot**
|
||||||
|
// @Description
|
||||||
|
// @Description May also upgrade package versions if name snapshot already contains packages being pulled. New snapshot `Destination` is created as result of this process.
|
||||||
|
// @Description If architectures are limited (with config architectures or parameter `Architectures`, only mentioned architectures are processed, otherwise aptly will process all architectures in the snapshot.
|
||||||
|
// @Description If following dependencies by source is enabled (using dependencyFollowSource config), pulling binary packages would also pull corresponding source packages as well.
|
||||||
|
// @Description By default aptly would remove packages matching name and architecture while importing: e.g. when importing software_1.3_amd64, package software_1.2.9_amd64 would be removed.
|
||||||
|
// @Description
|
||||||
|
// @Description With flag `no-remove` both package versions would stay in the snapshot.
|
||||||
|
// @Description
|
||||||
|
// @Description Aptly pulls first package matching each of package queries, but with flag -all-matches all matching packages would be pulled.
|
||||||
|
// @Tags Snapshots
|
||||||
|
// @Param name path string true "Name of the snapshot to be created"
|
||||||
|
// @Param all-matches query int false "pull all the packages that satisfy the dependency version requirements (default is to pull first matching package): 1 to enable"
|
||||||
|
// @Param dry-run query int false "don’t create destination snapshot, just show what would be pulled: 1 to enable"
|
||||||
|
// @Param no-deps query int false "don’t process dependencies, just pull listed packages: 1 to enable"
|
||||||
|
// @Param no-remove query int false "don’t remove other package versions when pulling package: 1 to enable"
|
||||||
|
// @Consume json
|
||||||
|
// @Param request body snapshotsPullParams true "Parameters"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200
|
||||||
|
// @Failure 400 {object} Error "Bad Request"
|
||||||
|
// @Failure 404 {object} Error "Not Found"
|
||||||
|
// @Failure 500 {object} Error "Internal Error"
|
||||||
|
// @Router /api/snapshots/{name}/pull [post]
|
||||||
|
func apiSnapshotsPull(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
destinationSnapshot *deb.Snapshot
|
||||||
|
body snapshotsPullParams
|
||||||
|
)
|
||||||
|
|
||||||
|
name := c.Params.ByName("name")
|
||||||
|
|
||||||
|
if err = c.BindJSON(&body); err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allMatches := c.Request.URL.Query().Get("all-matches") == "1"
|
||||||
|
dryRun := c.Request.URL.Query().Get("dry-run") == "1"
|
||||||
|
noDeps := c.Request.URL.Query().Get("no-deps") == "1"
|
||||||
|
noRemove := c.Request.URL.Query().Get("no-remove") == "1"
|
||||||
|
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
|
||||||
|
// Load <name> snapshot
|
||||||
|
toSnapshot, err := collectionFactory.SnapshotCollection().ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load <Source> snapshot
|
||||||
|
sourceSnapshot, err := collectionFactory.SnapshotCollection().ByName(body.Source)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())}
|
||||||
|
taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination)
|
||||||
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
// convert snapshots to package list
|
||||||
|
toPackageList, err := deb.NewPackageListFromRefList(toSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
sourcePackageList, err := deb.NewPackageListFromRefList(sourceSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toPackageList.PrepareIndex()
|
||||||
|
sourcePackageList.PrepareIndex()
|
||||||
|
|
||||||
|
var architecturesList []string
|
||||||
|
|
||||||
|
if len(context.ArchitecturesList()) > 0 {
|
||||||
|
architecturesList = context.ArchitecturesList()
|
||||||
|
} else {
|
||||||
|
architecturesList = toPackageList.Architectures(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
architecturesList = append(architecturesList, body.Architectures...)
|
||||||
|
sort.Strings(architecturesList)
|
||||||
|
|
||||||
|
if len(architecturesList) == 0 {
|
||||||
|
err := fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build architecture query: (arch == "i386" | arch == "amd64" | ...)
|
||||||
|
var archQuery deb.PackageQuery = &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: ""}
|
||||||
|
for _, arch := range architecturesList {
|
||||||
|
archQuery = &deb.OrQuery{L: &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: arch}, R: archQuery}
|
||||||
|
}
|
||||||
|
|
||||||
|
queries := make([]deb.PackageQuery, len(body.Queries))
|
||||||
|
for i, q := range body.Queries {
|
||||||
|
queries[i], err = query.Parse(q)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
// Add architecture filter
|
||||||
|
queries[i] = &deb.AndQuery{L: queries[i], R: archQuery}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter with dependencies as requested
|
||||||
|
destinationPackageList, err := sourcePackageList.FilterWithProgress(queries, !noDeps, toPackageList, context.DependencyOptions(), architecturesList, context.Progress())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
destinationPackageList.PrepareIndex()
|
||||||
|
|
||||||
|
removedPackages := []string{}
|
||||||
|
addedPackages := []string{}
|
||||||
|
alreadySeen := map[string]bool{}
|
||||||
|
|
||||||
|
destinationPackageList.ForEachIndexed(func(pkg *deb.Package) error {
|
||||||
|
key := pkg.Architecture + "_" + pkg.Name
|
||||||
|
_, seen := alreadySeen[key]
|
||||||
|
|
||||||
|
// If we haven't seen such name-architecture pair and were instructed to remove, remove it
|
||||||
|
if !noRemove && !seen {
|
||||||
|
// Remove all packages with the same name and architecture
|
||||||
|
packageSearchResults := toPackageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true)
|
||||||
|
for _, p := range packageSearchResults {
|
||||||
|
toPackageList.Remove(p)
|
||||||
|
removedPackages = append(removedPackages, p.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If !allMatches, add only first matching name-arch package
|
||||||
|
if !seen || allMatches {
|
||||||
|
toPackageList.Add(pkg)
|
||||||
|
addedPackages = append(addedPackages, pkg.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadySeen[key] = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
alreadySeen = nil
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
response := struct {
|
||||||
|
AddedPackages []string `json:"added_packages"`
|
||||||
|
RemovedPackages []string `json:"removed_packages"`
|
||||||
|
}{
|
||||||
|
AddedPackages: addedPackages,
|
||||||
|
RemovedPackages: removedPackages,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: response}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create <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, ", ")))
|
||||||
|
|
||||||
|
err = collectionFactory.SnapshotCollection().Add(destinationSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: destinationSnapshot}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type diskFree struct {
|
||||||
|
// Storage size [MiB]
|
||||||
|
Total uint64
|
||||||
|
// Available Storage [MiB]
|
||||||
|
Free uint64
|
||||||
|
// Percentage Full
|
||||||
|
PercentFull float32
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Get Storage Utilization
|
||||||
|
// @Description **Get disk free information of aptly storage**
|
||||||
|
// @Tags Status
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} diskFree "Storage information"
|
||||||
|
// @Failure 400 {object} Error "Internal Error"
|
||||||
|
// @Router /api/storage [get]
|
||||||
|
func apiDiskFree(c *gin.Context) {
|
||||||
|
var df diskFree
|
||||||
|
|
||||||
|
fs := context.Config().GetRootDir()
|
||||||
|
|
||||||
|
var stat syscall.Statfs_t
|
||||||
|
err := syscall.Statfs(fs, &stat)
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, 400, fmt.Errorf("Error getting storage info on %s: %s", fs, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
df.Total = uint64(stat.Blocks) * uint64(stat.Bsize) / 1048576
|
||||||
|
df.Free = uint64(stat.Bavail) * uint64(stat.Bsize) / 1048576
|
||||||
|
df.PercentFull = 100.0 - float32(stat.Bavail)/float32(stat.Blocks)*100.0
|
||||||
|
|
||||||
|
c.JSON(200, df)
|
||||||
|
}
|
||||||
+19
-15
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -10,7 +9,12 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /tasks
|
// @Summary Get tasks
|
||||||
|
// @Description Get list of available tasks. Each task is returned as in “show” API.
|
||||||
|
// @Tags Tasks
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} task.Task
|
||||||
|
// @Router /api/tasks [get]
|
||||||
func apiTasksList(c *gin.Context) {
|
func apiTasksList(c *gin.Context) {
|
||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
c.JSON(200, list.GetTasks())
|
c.JSON(200, list.GetTasks())
|
||||||
@@ -35,13 +39,13 @@ func apiTasksWaitForTaskByID(c *gin.Context) {
|
|||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
task, err := list.WaitForTaskByID(int(id))
|
task, err := list.WaitForTaskByID(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,14 +57,14 @@ func apiTasksShow(c *gin.Context) {
|
|||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var task task.Task
|
var task task.Task
|
||||||
task, err = list.GetTaskByID(int(id))
|
task, err = list.GetTaskByID(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,14 +76,14 @@ func apiTasksOutputShow(c *gin.Context) {
|
|||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var output string
|
var output string
|
||||||
output, err = list.GetTaskOutputByID(int(id))
|
output, err = list.GetTaskOutputByID(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,14 +95,14 @@ func apiTasksDetailShow(c *gin.Context) {
|
|||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var detail interface{}
|
var detail interface{}
|
||||||
detail, err = list.GetTaskDetailByID(int(id))
|
detail, err = list.GetTaskDetailByID(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,13 +114,13 @@ func apiTasksReturnValueShow(c *gin.Context) {
|
|||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := list.GetTaskReturnValueByID(int(id))
|
output, err := list.GetTaskReturnValueByID(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,14 +132,14 @@ func apiTasksDelete(c *gin.Context) {
|
|||||||
list := context.TaskList()
|
list := context.TaskList()
|
||||||
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, err)
|
AbortWithJSONError(c, 500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var delTask task.Task
|
var delTask task.Task
|
||||||
delTask, err = list.DeleteTaskByID(int(id))
|
delTask, err = list.DeleteTaskByID(int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(400, err)
|
AbortWithJSONError(c, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +149,7 @@ func apiTasksDelete(c *gin.Context) {
|
|||||||
// POST /tasks-dummy
|
// POST /tasks-dummy
|
||||||
func apiTasksDummy(c *gin.Context) {
|
func apiTasksDummy(c *gin.Context) {
|
||||||
resources := []string{"dummy"}
|
resources := []string{"dummy"}
|
||||||
taskName := fmt.Sprintf("Dummy task")
|
taskName := "Dummy task"
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
out.Printf("Dummy task started\n")
|
out.Printf("Dummy task started\n")
|
||||||
detail.Store([]int{1, 2, 3})
|
detail.Store([]int{1, 2, 3})
|
||||||
|
|||||||
+1
-1
@@ -71,5 +71,5 @@ func (s *TaskSuite) TestTasksClear(c *C) {
|
|||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
response, _ = s.HTTPRequest("GET", "/api/tasks", nil)
|
response, _ = s.HTTPRequest("GET", "/api/tasks", nil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
c.Check(response.Body.String(), Equals, "null")
|
c.Check(response.Body.String(), Equals, "[]")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=APT repository API
|
|
||||||
After=network.target
|
|
||||||
Documentation=man:aptly(1)
|
|
||||||
Documentation=https://www.aptly.info/doc/api/
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/bin/aptly api serve -no-lock -listen=127.0.0.1:8081
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=APT repository server
|
|
||||||
After=network.target
|
|
||||||
Documentation=man:aptly(1)
|
|
||||||
Documentation=https://www.aptly.info/doc/commands/
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/bin/aptly serve -listen=127.0.0.1:8080
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package aptly
|
||||||
|
|
||||||
|
var DistributionFocal = "focal"
|
||||||
+5
-3
@@ -35,8 +35,8 @@ type PackagePool interface {
|
|||||||
Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage ChecksumStorage) (path string, err error)
|
Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage ChecksumStorage) (path string, err error)
|
||||||
// LegacyPath returns legacy (pre 1.1) path to package file (relative to root)
|
// LegacyPath returns legacy (pre 1.1) path to package file (relative to root)
|
||||||
LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error)
|
LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error)
|
||||||
// Stat returns Unix stat(2) info
|
// Size returns the size of the given file in bytes.
|
||||||
Stat(path string) (os.FileInfo, error)
|
Size(path string) (size int64, err error)
|
||||||
// Open returns ReadSeekerCloser to access the file
|
// Open returns ReadSeekerCloser to access the file
|
||||||
Open(path string) (ReadSeekerCloser, error)
|
Open(path string) (ReadSeekerCloser, error)
|
||||||
// FilepathList returns file paths of all the files in the pool
|
// FilepathList returns file paths of all the files in the pool
|
||||||
@@ -47,6 +47,8 @@ type PackagePool interface {
|
|||||||
|
|
||||||
// LocalPackagePool is implemented by PackagePools residing on the same filesystem
|
// LocalPackagePool is implemented by PackagePools residing on the same filesystem
|
||||||
type LocalPackagePool interface {
|
type LocalPackagePool interface {
|
||||||
|
// Stat returns Unix stat(2) info
|
||||||
|
Stat(path string) (os.FileInfo, error)
|
||||||
// GenerateTempPath generates temporary path for download (which is fast to import into package pool later on)
|
// GenerateTempPath generates temporary path for download (which is fast to import into package pool later on)
|
||||||
GenerateTempPath(filename string) (string, error)
|
GenerateTempPath(filename string) (string, error)
|
||||||
// Link generates hardlink to destination path
|
// Link generates hardlink to destination path
|
||||||
@@ -70,7 +72,7 @@ type PublishedStorage interface {
|
|||||||
// Remove removes single file under public path
|
// Remove removes single file under public path
|
||||||
Remove(path string) error
|
Remove(path string) error
|
||||||
// LinkFromPool links package file from pool to dist's pool location
|
// LinkFromPool links package file from pool to dist's pool location
|
||||||
LinkFromPool(publishedDirectory, fileName string, sourcePool PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error
|
LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error
|
||||||
// Filelist returns list of files under prefix
|
// Filelist returns list of files under prefix
|
||||||
Filelist(prefix string) ([]string, error)
|
Filelist(prefix string) ([]string, error)
|
||||||
// RenameFile renames (moves) file
|
// RenameFile renames (moves) file
|
||||||
|
|||||||
+128
-1
@@ -1,2 +1,129 @@
|
|||||||
// Package azure handles publishing to Azure Storage
|
|
||||||
package azure
|
package azure
|
||||||
|
|
||||||
|
// Package azure handles publishing to Azure Storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isBlobNotFound(err error) bool {
|
||||||
|
storageError, ok := err.(azblob.StorageError)
|
||||||
|
return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
type azContext struct {
|
||||||
|
container azblob.ContainerURL
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
|
||||||
|
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint == "" {
|
||||||
|
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
container: containerURL,
|
||||||
|
prefix: prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
md5s = make([]string, 0, 1024)
|
||||||
|
prefix = filepath.Join(az.prefix, prefix)
|
||||||
|
if prefix != "" {
|
||||||
|
prefix += delimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths, md5s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
|
||||||
|
uploadOptions := azblob.UploadStreamToBlockBlobOptions{
|
||||||
|
BufferSize: 4 * 1024 * 1024,
|
||||||
|
MaxBuffers: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sourceMD5) > 0 {
|
||||||
|
decodedMD5, err := hex.DecodeString(sourceMD5)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
|
||||||
|
ContentMD5: decodedMD5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := azblob.UploadStreamToBlockBlob(
|
||||||
|
context.Background(),
|
||||||
|
source,
|
||||||
|
blob.ToBlockBlobURL(),
|
||||||
|
uploadOptions,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String
|
||||||
|
func (az *azContext) String() string {
|
||||||
|
return fmt.Sprintf("Azure: %s/%s", az.container, az.prefix)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package azure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackagePool struct {
|
||||||
|
az *azContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface
|
||||||
|
var (
|
||||||
|
_ aptly.PackagePool = (*PackagePool)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPackagePool creates published storage from Azure storage credentials
|
||||||
|
func NewPackagePool(accountName, accountKey, container, prefix, endpoint string) (*PackagePool, error) {
|
||||||
|
azctx, err := newAzContext(accountName, accountKey, container, prefix, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PackagePool{az: azctx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String
|
||||||
|
func (pool *PackagePool) String() string {
|
||||||
|
return pool.az.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.ChecksumInfo) string {
|
||||||
|
hash := checksums.SHA256
|
||||||
|
// Use the same path as the file pool, for compat reasons.
|
||||||
|
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) ensureChecksums(
|
||||||
|
poolPath string,
|
||||||
|
checksumStorage aptly.ChecksumStorage,
|
||||||
|
) (*utils.ChecksumInfo, error) {
|
||||||
|
targetChecksums, err := checksumStorage.Get(poolPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetChecksums == nil {
|
||||||
|
// we don't have checksums stored yet for this file
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrapf(err, "error downloading blob at %s", poolPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetChecksums = &utils.ChecksumInfo{}
|
||||||
|
*targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checksumStorage.Update(poolPath, targetChecksums)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetChecksums, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) {
|
||||||
|
if progress != nil {
|
||||||
|
progress.InitBar(0, false, aptly.BarGeneralBuildFileList)
|
||||||
|
defer progress.ShutdownBar()
|
||||||
|
}
|
||||||
|
|
||||||
|
paths, _, err := pool.az.internalFilelist("", progress)
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, error) {
|
||||||
|
return "", errors.New("Azure package pool does not support legacy paths")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Size(path string) (int64, error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Wrap(err, "error creating temporary file for blob download")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(temp.Name())
|
||||||
|
|
||||||
|
err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error downloading blob at %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Remove(path string) (int64, error) {
|
||||||
|
blob := pool.az.blobURL(path)
|
||||||
|
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
|
||||||
|
if checksums.MD5 == "" || checksums.SHA256 == "" || checksums.SHA512 == "" {
|
||||||
|
// need to update checksums, MD5 and SHA256 should be always defined
|
||||||
|
var err error
|
||||||
|
*checksums, err = utils.ChecksumsForFile(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path := pool.buildPoolPath(basename, checksums)
|
||||||
|
blob := pool.az.blobURL(path)
|
||||||
|
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if targetChecksums != nil {
|
||||||
|
// target already exists
|
||||||
|
*checksums = *targetChecksums
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := os.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
err = pool.az.putFile(blob, source, checksums.MD5)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checksums.Complete() {
|
||||||
|
// need full checksums here
|
||||||
|
*checksums, err = utils.ChecksumsForFile(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checksumStorage.Update(path, checksums)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) {
|
||||||
|
if poolPath == "" {
|
||||||
|
if checksums.SHA256 != "" {
|
||||||
|
poolPath = pool.buildPoolPath(basename, checksums)
|
||||||
|
} else {
|
||||||
|
// No checksums or pool path, so no idea what file to look for.
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size, err := pool.Size(poolPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if size != checksums.Size {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
targetChecksums, err := pool.ensureChecksums(poolPath, checksumStorage)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if targetChecksums == nil {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checksums.MD5 != "" && targetChecksums.MD5 != checksums.MD5 ||
|
||||||
|
checksums.SHA256 != "" && targetChecksums.SHA256 != checksums.SHA256 {
|
||||||
|
// wrong file?
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill back checksums
|
||||||
|
*checksums = *targetChecksums
|
||||||
|
return poolPath, true, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
package azure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackagePoolSuite struct {
|
||||||
|
accountName, accountKey, endpoint string
|
||||||
|
pool, prefixedPool *PackagePool
|
||||||
|
debFile string
|
||||||
|
cs aptly.ChecksumStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&PackagePoolSuite{})
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) SetUpSuite(c *C) {
|
||||||
|
s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
|
||||||
|
if s.accountName == "" {
|
||||||
|
println("Please set the the following two environment variables to run the Azure storage tests.")
|
||||||
|
println(" 1. AZURE_STORAGE_ACCOUNT")
|
||||||
|
println(" 2. AZURE_STORAGE_ACCESS_KEY")
|
||||||
|
c.Skip("AZURE_STORAGE_ACCOUNT not set.")
|
||||||
|
}
|
||||||
|
s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
|
||||||
|
if s.accountKey == "" {
|
||||||
|
println("Please set the the following two environment variables to run the Azure storage tests.")
|
||||||
|
println(" 1. AZURE_STORAGE_ACCOUNT")
|
||||||
|
println(" 2. AZURE_STORAGE_ACCESS_KEY")
|
||||||
|
c.Skip("AZURE_STORAGE_ACCESS_KEY not set.")
|
||||||
|
}
|
||||||
|
s.endpoint = os.Getenv("AZURE_STORAGE_ENDPOINT")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) SetUpTest(c *C) {
|
||||||
|
container := randContainer()
|
||||||
|
prefix := "lala"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
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)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
_, _File, _, _ := runtime.Caller(0)
|
||||||
|
s.debFile = filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb")
|
||||||
|
s.cs = files.NewMockChecksumStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestFilepathList(c *C) {
|
||||||
|
list, err := s.pool.FilepathList(nil)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(list, DeepEquals, []string{})
|
||||||
|
|
||||||
|
s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
|
||||||
|
list, err = s.pool.FilepathList(nil)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(list, DeepEquals, []string{
|
||||||
|
"c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb",
|
||||||
|
"c7/6b/4bd12fd92e4dfe1b55b18a67a669_b.deb",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestRemove(c *C) {
|
||||||
|
s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
|
||||||
|
size, err := s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb")
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(size, Equals, int64(2738))
|
||||||
|
|
||||||
|
_, err = s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb")
|
||||||
|
c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*")
|
||||||
|
|
||||||
|
list, err := s.pool.FilepathList(nil)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(list, DeepEquals, []string{"c7/6b/4bd12fd92e4dfe1b55b18a67a669_b.deb"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestImportOk(c *C) {
|
||||||
|
var checksum utils.ChecksumInfo
|
||||||
|
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
|
||||||
|
// SHA256 should be automatically calculated
|
||||||
|
c.Check(checksum.SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12")
|
||||||
|
// checksum storage is filled with new checksum
|
||||||
|
c.Check(s.cs.(*files.MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12")
|
||||||
|
|
||||||
|
size, err := s.pool.Size(path)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(size, Equals, int64(2738))
|
||||||
|
|
||||||
|
// import as different name
|
||||||
|
checksum = utils.ChecksumInfo{}
|
||||||
|
path, err = s.pool.Import(s.debFile, "some.deb", &checksum, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_some.deb")
|
||||||
|
// checksum storage is filled with new checksum
|
||||||
|
c.Check(s.cs.(*files.MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12")
|
||||||
|
|
||||||
|
// double import, should be ok
|
||||||
|
checksum = utils.ChecksumInfo{}
|
||||||
|
path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
|
||||||
|
// checksum is filled back based on checksum storage
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// clear checksum storage, and do double-import
|
||||||
|
delete(s.cs.(*files.MockChecksumStorage).Store, path)
|
||||||
|
checksum = utils.ChecksumInfo{}
|
||||||
|
path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
|
||||||
|
// checksum is filled back based on re-calculation of file in the pool
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// import under new name, but with path-relevant checksums already filled in
|
||||||
|
checksum = utils.ChecksumInfo{SHA256: checksum.SHA256}
|
||||||
|
path, err = s.pool.Import(s.debFile, "other.deb", &checksum, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_other.deb")
|
||||||
|
// checksum is filled back based on re-calculation of source file
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestVerify(c *C) {
|
||||||
|
// file doesn't exist yet
|
||||||
|
ppath, exists, err := s.pool.Verify("", filepath.Base(s.debFile), &utils.ChecksumInfo{}, s.cs)
|
||||||
|
c.Check(ppath, Equals, "")
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, false)
|
||||||
|
|
||||||
|
// import file
|
||||||
|
checksum := utils.ChecksumInfo{}
|
||||||
|
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
|
||||||
|
|
||||||
|
// check existence
|
||||||
|
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs)
|
||||||
|
c.Check(ppath, Equals, ppath)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, true)
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// check existence with fixed path
|
||||||
|
checksum = utils.ChecksumInfo{Size: checksum.Size}
|
||||||
|
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs)
|
||||||
|
c.Check(ppath, Equals, path)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, true)
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// check existence, but with checksums missing (that aren't needed to find the path)
|
||||||
|
checksum.SHA512 = ""
|
||||||
|
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs)
|
||||||
|
c.Check(ppath, Equals, path)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, true)
|
||||||
|
// checksum is filled back based on checksum storage
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// check existence, with missing checksum info but correct path and size available
|
||||||
|
checksum = utils.ChecksumInfo{Size: checksum.Size}
|
||||||
|
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs)
|
||||||
|
c.Check(ppath, Equals, path)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, true)
|
||||||
|
// checksum is filled back based on checksum storage
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// check existence, with wrong checksum info but correct path and size available
|
||||||
|
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &utils.ChecksumInfo{
|
||||||
|
SHA256: "abc",
|
||||||
|
Size: checksum.Size,
|
||||||
|
}, s.cs)
|
||||||
|
c.Check(ppath, Equals, "")
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, false)
|
||||||
|
|
||||||
|
// check existence, with missing checksums (that aren't needed to find the path)
|
||||||
|
// and no info in checksum storage
|
||||||
|
delete(s.cs.(*files.MockChecksumStorage).Store, path)
|
||||||
|
checksum.SHA512 = ""
|
||||||
|
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs)
|
||||||
|
c.Check(ppath, Equals, path)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, true)
|
||||||
|
// checksum is filled back based on re-calculation
|
||||||
|
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
|
||||||
|
|
||||||
|
// check existence, with wrong size
|
||||||
|
checksum = utils.ChecksumInfo{Size: 13455}
|
||||||
|
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs)
|
||||||
|
c.Check(ppath, Equals, "")
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, false)
|
||||||
|
|
||||||
|
// check existence, with empty checksum info
|
||||||
|
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &utils.ChecksumInfo{}, s.cs)
|
||||||
|
c.Check(ppath, Equals, "")
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(exists, Equals, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestImportNotExist(c *C) {
|
||||||
|
_, err := s.pool.Import("no-such-file", "a.deb", &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
c.Check(err, ErrorMatches, ".*no such file or directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestSize(c *C) {
|
||||||
|
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
|
size, err := s.pool.Size(path)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(size, Equals, int64(2738))
|
||||||
|
|
||||||
|
_, err = s.pool.Size("do/es/ntexist")
|
||||||
|
c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PackagePoolSuite) TestOpen(c *C) {
|
||||||
|
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &utils.ChecksumInfo{}, false, s.cs)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
|
f, err := s.pool.Open(path)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
contents, err := ioutil.ReadAll(f)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Check(len(contents), Equals, 2738)
|
||||||
|
c.Check(f.Close(), IsNil)
|
||||||
|
|
||||||
|
_, err = s.pool.Open("do/es/ntexist")
|
||||||
|
c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*")
|
||||||
|
}
|
||||||
+34
-131
@@ -2,12 +2,8 @@ package azure
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@@ -22,7 +18,8 @@ import (
|
|||||||
type PublishedStorage struct {
|
type PublishedStorage struct {
|
||||||
container azblob.ContainerURL
|
container azblob.ContainerURL
|
||||||
prefix string
|
prefix string
|
||||||
pathCache map[string]string
|
az *azContext
|
||||||
|
pathCache map[string]map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check interface
|
// Check interface
|
||||||
@@ -30,60 +27,23 @@ var (
|
|||||||
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
|
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func isEmulatorEndpoint(endpoint string) bool {
|
|
||||||
if h, _, err := net.SplitHostPort(endpoint); err == nil {
|
|
||||||
endpoint = h
|
|
||||||
}
|
|
||||||
if endpoint == "localhost" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// For IPv6, there could be case where SplitHostPort fails for cannot finding port.
|
|
||||||
// In this case, eliminate the '[' and ']' in the URL.
|
|
||||||
// For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732
|
|
||||||
if endpoint[0] == '[' && endpoint[len(endpoint)-1] == ']' {
|
|
||||||
endpoint = endpoint[1 : len(endpoint)-1]
|
|
||||||
}
|
|
||||||
return net.ParseIP(endpoint) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPublishedStorage creates published storage from Azure storage credentials
|
// NewPublishedStorage creates published storage from Azure storage credentials
|
||||||
func NewPublishedStorage(accountName, accountKey, container, prefix, endpoint string) (*PublishedStorage, error) {
|
func NewPublishedStorage(accountName, accountKey, container, prefix, endpoint string) (*PublishedStorage, error) {
|
||||||
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
azctx, err := newAzContext(accountName, accountKey, container, prefix, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpoint == "" {
|
return &PublishedStorage{az: azctx}, nil
|
||||||
endpoint = "blob.core.windows.net"
|
|
||||||
}
|
|
||||||
|
|
||||||
var url *url.URL
|
|
||||||
if isEmulatorEndpoint(endpoint) {
|
|
||||||
url, err = url.Parse(fmt.Sprintf("http://%s/%s/%s", endpoint, accountName, container))
|
|
||||||
} else {
|
|
||||||
url, err = url.Parse(fmt.Sprintf("https://%s.%s/%s", accountName, endpoint, container))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
|
|
||||||
|
|
||||||
result := &PublishedStorage{
|
|
||||||
container: containerURL,
|
|
||||||
prefix: prefix,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String
|
// String
|
||||||
func (storage *PublishedStorage) String() string {
|
func (storage *PublishedStorage) String() string {
|
||||||
return fmt.Sprintf("Azure: %s/%s", storage.container, storage.prefix)
|
return storage.az.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkDir creates directory recursively under public path
|
// MkDir creates directory recursively under public path
|
||||||
func (storage *PublishedStorage) MkDir(path string) error {
|
func (storage *PublishedStorage) MkDir(_ string) error {
|
||||||
// no op for Azure
|
// no op for Azure
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -106,7 +66,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
|||||||
}
|
}
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
err = storage.putFile(path, source, sourceMD5)
|
err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
||||||
}
|
}
|
||||||
@@ -114,45 +74,15 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// putFile uploads file-like object to
|
|
||||||
func (storage *PublishedStorage) putFile(path string, source io.Reader, sourceMD5 string) error {
|
|
||||||
path = filepath.Join(storage.prefix, path)
|
|
||||||
|
|
||||||
blob := storage.container.NewBlockBlobURL(path)
|
|
||||||
|
|
||||||
uploadOptions := azblob.UploadStreamToBlockBlobOptions{
|
|
||||||
BufferSize: 4 * 1024 * 1024,
|
|
||||||
MaxBuffers: 8,
|
|
||||||
}
|
|
||||||
if len(sourceMD5) > 0 {
|
|
||||||
decodedMD5, err := hex.DecodeString(sourceMD5)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
|
|
||||||
ContentMD5: decodedMD5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := azblob.UploadStreamToBlockBlob(
|
|
||||||
context.Background(),
|
|
||||||
source,
|
|
||||||
blob,
|
|
||||||
uploadOptions,
|
|
||||||
)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveDirs removes directory structure under public path
|
// RemoveDirs removes directory structure under public path
|
||||||
func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error {
|
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
|
||||||
filelist, err := storage.Filelist(path)
|
filelist, err := storage.Filelist(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range filelist {
|
for _, filename := range filelist {
|
||||||
blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path, filename))
|
blob := storage.az.blobURL(filepath.Join(path, filename))
|
||||||
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
||||||
@@ -164,7 +94,7 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress
|
|||||||
|
|
||||||
// Remove removes single file under public path
|
// Remove removes single file under public path
|
||||||
func (storage *PublishedStorage) Remove(path string) error {
|
func (storage *PublishedStorage) Remove(path string) error {
|
||||||
blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path))
|
blob := storage.az.blobURL(path)
|
||||||
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
||||||
@@ -174,31 +104,39 @@ func (storage *PublishedStorage) Remove(path string) error {
|
|||||||
|
|
||||||
// LinkFromPool links package file from pool to dist's pool location
|
// LinkFromPool links package file from pool to dist's pool location
|
||||||
//
|
//
|
||||||
// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/)
|
// publishedPrefix is desired prefix for the location in the pool.
|
||||||
|
// publishedRelPath is desired location in pool (like pool/component/liba/libav/)
|
||||||
// sourcePool is instance of aptly.PackagePool
|
// sourcePool is instance of aptly.PackagePool
|
||||||
// sourcePath is filepath to package file in package pool
|
// sourcePath is filepath to package file in package pool
|
||||||
//
|
//
|
||||||
// LinkFromPool returns relative path for the published file to be included in package index
|
// LinkFromPool returns relative path for the published file to be included in package index
|
||||||
func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool,
|
func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool,
|
||||||
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
||||||
|
|
||||||
relPath := filepath.Join(publishedDirectory, fileName)
|
relFilePath := filepath.Join(publishedRelPath, fileName)
|
||||||
poolPath := filepath.Join(storage.prefix, relPath)
|
// prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||||
|
// FIXME: check how to integrate publishedPrefix:
|
||||||
|
poolPath := storage.az.blobPath(fileName)
|
||||||
|
|
||||||
if storage.pathCache == nil {
|
if storage.pathCache == nil {
|
||||||
paths, md5s, err := storage.internalFilelist("")
|
storage.pathCache = make(map[string]map[string]string)
|
||||||
|
}
|
||||||
|
pathCache := storage.pathCache[publishedPrefix]
|
||||||
|
if pathCache == nil {
|
||||||
|
paths, md5s, err := storage.az.internalFilelist(publishedPrefix, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error caching paths under prefix: %s", err)
|
return fmt.Errorf("error caching paths under prefix: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.pathCache = make(map[string]string, len(paths))
|
pathCache = make(map[string]string, len(paths))
|
||||||
|
|
||||||
for i := range paths {
|
for i := range paths {
|
||||||
storage.pathCache[paths[i]] = md5s[i]
|
pathCache[paths[i]] = md5s[i]
|
||||||
}
|
}
|
||||||
|
storage.pathCache[publishedPrefix] = pathCache
|
||||||
}
|
}
|
||||||
|
|
||||||
destinationMD5, exists := storage.pathCache[relPath]
|
destinationMD5, exists := pathCache[relFilePath]
|
||||||
sourceMD5 := sourceChecksums.MD5
|
sourceMD5 := sourceChecksums.MD5
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
@@ -221,9 +159,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin
|
|||||||
}
|
}
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
err = storage.putFile(relPath, source, sourceMD5)
|
err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
storage.pathCache[relPath] = sourceMD5
|
pathCache[relFilePath] = sourceMD5
|
||||||
} else {
|
} else {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s: %s", sourcePath, storage, poolPath))
|
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s: %s", sourcePath, storage, poolPath))
|
||||||
}
|
}
|
||||||
@@ -231,43 +169,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName strin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *PublishedStorage) internalFilelist(prefix string) (paths []string, md5s []string, err error) {
|
|
||||||
const delimiter = "/"
|
|
||||||
paths = make([]string, 0, 1024)
|
|
||||||
md5s = make([]string, 0, 1024)
|
|
||||||
prefix = filepath.Join(storage.prefix, prefix)
|
|
||||||
if prefix != "" {
|
|
||||||
prefix += delimiter
|
|
||||||
}
|
|
||||||
|
|
||||||
for marker := (azblob.Marker{}); marker.NotDone(); {
|
|
||||||
listBlob, err := storage.container.ListBlobsFlatSegment(
|
|
||||||
context.Background(), marker, azblob.ListBlobsSegmentOptions{
|
|
||||||
Prefix: prefix,
|
|
||||||
MaxResults: 1000,
|
|
||||||
Details: azblob.BlobListingDetails{Metadata: true}})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, storage, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
marker = listBlob.NextMarker
|
|
||||||
|
|
||||||
for _, blob := range listBlob.Segment.BlobItems {
|
|
||||||
if prefix == "" {
|
|
||||||
paths = append(paths, blob.Name)
|
|
||||||
} else {
|
|
||||||
paths = append(paths, blob.Name[len(prefix):])
|
|
||||||
}
|
|
||||||
md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths, md5s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filelist returns list of files under prefix
|
// Filelist returns list of files under prefix
|
||||||
func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
||||||
paths, _, err := storage.internalFilelist(prefix)
|
paths, _, err := storage.az.internalFilelist(prefix, nil)
|
||||||
return paths, err
|
return paths, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,8 +179,8 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
|||||||
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
|
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
|
||||||
const leaseDuration = 30
|
const leaseDuration = 30
|
||||||
|
|
||||||
dstBlobURL := storage.container.NewBlobURL(filepath.Join(storage.prefix, dst))
|
dstBlobURL := storage.az.blobURL(dst)
|
||||||
srcBlobURL := storage.container.NewBlobURL(filepath.Join(storage.prefix, src))
|
srcBlobURL := storage.az.blobURL(src)
|
||||||
leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
|
leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
|
||||||
if err != nil || leaseResp.StatusCode() != http.StatusCreated {
|
if err != nil || leaseResp.StatusCode() != http.StatusCreated {
|
||||||
return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
|
return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
|
||||||
@@ -347,11 +251,10 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
|
|||||||
|
|
||||||
// FileExists returns true if path exists
|
// FileExists returns true if path exists
|
||||||
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
||||||
blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path))
|
blob := storage.az.blobURL(path)
|
||||||
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
storageError, ok := err.(azblob.StorageError)
|
if isBlobNotFound(err) {
|
||||||
if ok && string(storageError.ServiceCode()) == string(azblob.StorageErrorCodeBlobNotFound) {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
@@ -364,7 +267,7 @@ func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
|||||||
// ReadLink returns the symbolic link pointed to by path.
|
// ReadLink returns the symbolic link pointed to by path.
|
||||||
// This simply reads text file created with SymLink
|
// This simply reads text file created with SymLink
|
||||||
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
||||||
blob := storage.container.NewBlobURL(filepath.Join(storage.prefix, path))
|
blob := storage.az.blobURL(path)
|
||||||
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
+15
-15
@@ -43,14 +43,14 @@ func randString(n int) string {
|
|||||||
func (s *PublishedStorageSuite) SetUpSuite(c *C) {
|
func (s *PublishedStorageSuite) SetUpSuite(c *C) {
|
||||||
s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
|
s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
|
||||||
if s.accountName == "" {
|
if s.accountName == "" {
|
||||||
println("Please set the the following two environment variables to run the Azure storage tests.")
|
println("Please set the following two environment variables to run the Azure storage tests.")
|
||||||
println(" 1. AZURE_STORAGE_ACCOUNT")
|
println(" 1. AZURE_STORAGE_ACCOUNT")
|
||||||
println(" 2. AZURE_STORAGE_ACCESS_KEY")
|
println(" 2. AZURE_STORAGE_ACCESS_KEY")
|
||||||
c.Skip("AZURE_STORAGE_ACCOUNT not set.")
|
c.Skip("AZURE_STORAGE_ACCOUNT not set.")
|
||||||
}
|
}
|
||||||
s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
|
s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
|
||||||
if s.accountKey == "" {
|
if s.accountKey == "" {
|
||||||
println("Please set the the following two environment variables to run the Azure storage tests.")
|
println("Please set the following two environment variables to run the Azure storage tests.")
|
||||||
println(" 1. AZURE_STORAGE_ACCOUNT")
|
println(" 1. AZURE_STORAGE_ACCOUNT")
|
||||||
println(" 2. AZURE_STORAGE_ACCESS_KEY")
|
println(" 2. AZURE_STORAGE_ACCESS_KEY")
|
||||||
c.Skip("AZURE_STORAGE_ACCESS_KEY not set.")
|
c.Skip("AZURE_STORAGE_ACCESS_KEY not set.")
|
||||||
@@ -66,7 +66,7 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
cnt := s.storage.container
|
cnt := s.storage.az.container
|
||||||
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
@@ -75,13 +75,13 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
||||||
cnt := s.storage.container
|
cnt := s.storage.az.container
|
||||||
_, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
|
_, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
||||||
blob := s.storage.container.NewBlobURL(path)
|
blob := s.storage.az.container.NewBlobURL(path)
|
||||||
resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
|
body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
|
||||||
@@ -91,7 +91,7 @@ func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
||||||
_, err := s.storage.container.NewBlobURL(path).GetProperties(
|
_, err := s.storage.az.container.NewBlobURL(path).GetProperties(
|
||||||
context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
c.Assert(err, NotNil)
|
c.Assert(err, NotNil)
|
||||||
storageError, ok := err.(azblob.StorageError)
|
storageError, ok := err.(azblob.StorageError)
|
||||||
@@ -104,7 +104,7 @@ func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
|||||||
_, err := azblob.UploadBufferToBlockBlob(
|
_, err := azblob.UploadBufferToBlockBlob(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
data,
|
data,
|
||||||
s.storage.container.NewBlockBlobURL(path),
|
s.storage.az.container.NewBlockBlobURL(path),
|
||||||
azblob.UploadToBlockBlobOptions{
|
azblob.UploadToBlockBlobOptions{
|
||||||
BlobHTTPHeaders: azblob.BlobHTTPHeaders{
|
BlobHTTPHeaders: azblob.BlobHTTPHeaders{
|
||||||
ContentMD5: hash[:],
|
ContentMD5: hash[:],
|
||||||
@@ -129,7 +129,7 @@ func (s *PublishedStorageSuite) TestPutFile(c *C) {
|
|||||||
err = s.prefixedStorage.PutFile(filename, filepath.Join(dir, "a"))
|
err = s.prefixedStorage.PutFile(filename, filepath.Join(dir, "a"))
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
c.Check(s.GetFile(c, filepath.Join(s.prefixedStorage.prefix, filename)), DeepEquals, content)
|
c.Check(s.GetFile(c, filepath.Join(s.prefixedStorage.az.prefix, filename)), DeepEquals, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) TestPutFilePlus(c *C) {
|
func (s *PublishedStorageSuite) TestPutFilePlus(c *C) {
|
||||||
@@ -300,45 +300,45 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
// first link from pool
|
// first link from pool
|
||||||
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
|
err = s.storage.LinkFromPool("", filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
||||||
|
|
||||||
// duplicate link from pool
|
// duplicate link from pool
|
||||||
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
|
err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
||||||
|
|
||||||
// link from pool with conflict
|
// link from pool with conflict
|
||||||
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false)
|
err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false)
|
||||||
c.Check(err, ErrorMatches, ".*file already exists and is different.*")
|
c.Check(err, ErrorMatches, ".*file already exists and is different.*")
|
||||||
|
|
||||||
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
||||||
|
|
||||||
// link from pool with conflict and force
|
// link from pool with conflict and force
|
||||||
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true)
|
err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Spam"))
|
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Spam"))
|
||||||
|
|
||||||
// for prefixed storage:
|
// for prefixed storage:
|
||||||
// first link from pool
|
// first link from pool
|
||||||
err = s.prefixedStorage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
|
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
// 2nd link from pool, providing wrong path for source file
|
// 2nd link from pool, providing wrong path for source file
|
||||||
//
|
//
|
||||||
// this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
|
// this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
|
||||||
s.prefixedStorage.pathCache = nil
|
s.prefixedStorage.pathCache = nil
|
||||||
err = s.prefixedStorage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
c.Check(s.GetFile(c, "lala/pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
c.Check(s.GetFile(c, "lala/pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
|
||||||
|
|
||||||
// link from pool with nested file name
|
// link from pool with nested file name
|
||||||
err = s.storage.LinkFromPool("dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false)
|
err = s.storage.LinkFromPool("", "dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
c.Check(s.GetFile(c, "dists/jessie/non-free/installer-i386/current/images/netboot/boot.img.gz"), DeepEquals, []byte("Contents"))
|
c.Check(s.GetFile(c, "dists/jessie/non-free/installer-i386/current/images/netboot/boot.img.gz"), DeepEquals, []byte("Contents"))
|
||||||
|
|||||||
+22
-9
@@ -1,11 +1,15 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
stdcontext "context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/api"
|
"github.com/aptly-dev/aptly/api"
|
||||||
"github.com/aptly-dev/aptly/systemd/activation"
|
"github.com/aptly-dev/aptly/systemd/activation"
|
||||||
@@ -30,7 +34,7 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
|||||||
// anything else must fail.
|
// anything else must fail.
|
||||||
// E.g.: Running the service under a different user may lead to a rootDir
|
// E.g.: Running the service under a different user may lead to a rootDir
|
||||||
// that exists but is not usable due to access permissions.
|
// that exists but is not usable due to access permissions.
|
||||||
err = utils.DirIsAccessible(context.Config().RootDir)
|
err = utils.DirIsAccessible(context.Config().GetRootDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -55,6 +59,17 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
|||||||
listen := context.Flags().Lookup("listen").Value.String()
|
listen := context.Flags().Lookup("listen").Value.String()
|
||||||
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
|
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
|
||||||
|
|
||||||
|
server := http.Server{Handler: api.Router(context)}
|
||||||
|
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
go (func() {
|
||||||
|
if _, ok := <-sigchan; ok {
|
||||||
|
server.Shutdown(stdcontext.Background())
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
defer close(sigchan)
|
||||||
|
|
||||||
listenURL, err := url.Parse(listen)
|
listenURL, err := url.Parse(listen)
|
||||||
if err == nil && listenURL.Scheme == "unix" {
|
if err == nil && listenURL.Scheme == "unix" {
|
||||||
file := listenURL.Path
|
file := listenURL.Path
|
||||||
@@ -67,19 +82,17 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
err = http.Serve(listener, api.Router(context))
|
err = server.Serve(listener)
|
||||||
if err != nil {
|
} else {
|
||||||
return fmt.Errorf("unable to serve: %s", err)
|
server.Addr = listen
|
||||||
}
|
err = server.ListenAndServe()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = http.ListenAndServe(listen, api.Router(context))
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to serve: %s", err)
|
return fmt.Errorf("unable to serve: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCmdAPIServe() *commander.Command {
|
func makeCmdAPIServe() *commander.Command {
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ package environment to new version.`,
|
|||||||
cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if dependency is 'a|b'")
|
cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if dependency is 'a|b'")
|
||||||
cmd.Flag.Bool("dep-verbose-resolve", false, "when processing dependencies, print detailed logs")
|
cmd.Flag.Bool("dep-verbose-resolve", false, "when processing dependencies, print detailed logs")
|
||||||
cmd.Flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available")
|
cmd.Flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available")
|
||||||
cmd.Flag.String("config", "", "location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf)")
|
cmd.Flag.String("config", "", "location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf)")
|
||||||
cmd.Flag.String("gpg-provider", "", "PGP implementation (\"gpg\", \"gpg1\", \"gpg2\" for external gpg or \"internal\" for Go internal implementation)")
|
cmd.Flag.String("gpg-provider", "", "PGP implementation (\"gpg\", \"gpg1\", \"gpg2\" for external gpg or \"internal\" for Go internal implementation)")
|
||||||
|
|
||||||
if aptly.EnableDebug {
|
if aptly.EnableDebug {
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
)
|
)
|
||||||
|
|
||||||
func aptlyConfigShow(cmd *commander.Command, args []string) error {
|
func aptlyConfigShow(_ *commander.Command, _ []string) error {
|
||||||
|
|
||||||
config := context.Config()
|
config := context.Config()
|
||||||
prettyJSON, err := json.MarshalIndent(config, "", " ")
|
prettyJSON, err := json.MarshalIndent(config, "", " ")
|
||||||
|
|||||||
+1
-2
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -36,7 +35,7 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
buf := bytes.NewBufferString(graph.String())
|
buf := bytes.NewBufferString(graph.String())
|
||||||
|
|
||||||
tempfile, err := ioutil.TempFile("", "aptly-graph")
|
tempfile, err := os.CreateTemp("", "aptly-graph")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -9,18 +9,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getVerifier(flags *flag.FlagSet) (pgp.Verifier, error) {
|
func getVerifier(flags *flag.FlagSet) (pgp.Verifier, error) {
|
||||||
if LookupOption(context.Config().GpgDisableVerify, flags, "ignore-signatures") {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
keyRings := flags.Lookup("keyring").Value.Get().([]string)
|
keyRings := flags.Lookup("keyring").Value.Get().([]string)
|
||||||
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
|
if context.Flags().IsSet("ignore-signatures") {
|
||||||
|
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||||
|
}
|
||||||
|
|
||||||
verifier := context.GetVerifier()
|
verifier := context.GetVerifier()
|
||||||
for _, keyRing := range keyRings {
|
for _, keyRing := range keyRings {
|
||||||
verifier.AddKeyring(keyRing)
|
verifier.AddKeyring(keyRing)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := verifier.InitKeyring()
|
err := verifier.InitKeyring(ignoreSignatures == false) // be verbose only if verifying signatures is requested
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
|||||||
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
||||||
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
||||||
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
|
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
|
||||||
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
|
if context.Flags().IsSet("ignore-signatures") {
|
||||||
|
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mirrorName, archiveURL, distribution string
|
mirrorName, archiveURL, distribution string
|
||||||
@@ -59,7 +63,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo.Fetch(context.Downloader(), verifier)
|
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to fetch mirror: %s", err)
|
return fmt.Errorf("unable to fetch mirror: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -28,6 +28,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchMirror := false
|
fetchMirror := false
|
||||||
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
context.Flags().Visit(func(flag *flag.Flag) {
|
context.Flags().Visit(func(flag *flag.Flag) {
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
case "filter":
|
case "filter":
|
||||||
@@ -43,6 +44,8 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
case "archive-url":
|
case "archive-url":
|
||||||
repo.SetArchiveRoot(flag.Value.String())
|
repo.SetArchiveRoot(flag.Value.String())
|
||||||
fetchMirror = true
|
fetchMirror = true
|
||||||
|
case "ignore-signatures":
|
||||||
|
ignoreSignatures = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -69,7 +72,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo.Fetch(context.Downloader(), verifier)
|
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to edit: %s", err)
|
return fmt.Errorf("unable to edit: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
|
|||||||
return aptlyMirrorListTxt(cmd, args)
|
return aptlyMirrorListTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyMirrorListTxt(cmd *commander.Command, args []string) error {
|
func aptlyMirrorListTxt(cmd *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||||
@@ -65,7 +65,7 @@ func aptlyMirrorListTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyMirrorListJSON(cmd *commander.Command, args []string) error {
|
func aptlyMirrorListJSON(_ *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
repos := make([]*deb.RemoteRepo, context.NewCollectionFactory().RemoteRepoCollection().Len())
|
repos := make([]*deb.RemoteRepo, context.NewCollectionFactory().RemoteRepoCollection().Len())
|
||||||
|
|||||||
+2
-2
@@ -27,7 +27,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
|||||||
return aptlyMirrorShowTxt(cmd, args)
|
return aptlyMirrorShowTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyMirrorShowTxt(cmd *commander.Command, args []string) error {
|
func aptlyMirrorShowTxt(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
@@ -93,7 +93,7 @@ func aptlyMirrorShowTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyMirrorShowJSON(cmd *commander.Command, args []string) error {
|
func aptlyMirrorShowJSON(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|||||||
+31
-5
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -41,20 +42,24 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
|
if context.Flags().IsSet("ignore-signatures") {
|
||||||
|
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||||
|
}
|
||||||
|
ignoreChecksums := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
|
||||||
|
|
||||||
verifier, err := getVerifier(context.Flags())
|
verifier, err := getVerifier(context.Flags())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo.Fetch(context.Downloader(), verifier)
|
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Progress().Printf("Downloading & parsing package files...\n")
|
context.Progress().Printf("Downloading & parsing package files...\n")
|
||||||
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), verifier, collectionFactory, ignoreMismatch)
|
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), verifier, collectionFactory, ignoreSignatures, ignoreChecksums)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -161,7 +166,16 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
var e error
|
var e error
|
||||||
|
|
||||||
// provision download location
|
// provision download location
|
||||||
task.TempDownPath, e = context.PackagePool().(aptly.LocalPackagePool).GenerateTempPath(task.File.Filename)
|
if pp, ok := context.PackagePool().(aptly.LocalPackagePool); ok {
|
||||||
|
task.TempDownPath, e = pp.GenerateTempPath(task.File.Filename)
|
||||||
|
} else {
|
||||||
|
var file *os.File
|
||||||
|
file, e = os.CreateTemp("", task.File.Filename)
|
||||||
|
if e == nil {
|
||||||
|
task.TempDownPath = file.Name()
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
if e != nil {
|
if e != nil {
|
||||||
pushError(e)
|
pushError(e)
|
||||||
continue
|
continue
|
||||||
@@ -173,7 +187,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
repo.PackageURL(task.File.DownloadURL()).String(),
|
repo.PackageURL(task.File.DownloadURL()).String(),
|
||||||
task.TempDownPath,
|
task.TempDownPath,
|
||||||
&task.File.Checksums,
|
&task.File.Checksums,
|
||||||
ignoreMismatch)
|
ignoreChecksums)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
pushError(e)
|
pushError(e)
|
||||||
continue
|
continue
|
||||||
@@ -197,6 +211,18 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, task := range queue {
|
||||||
|
if task.TempDownPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(task.TempDownPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to delete %s: %v\n", task.TempDownPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Import downloaded files
|
// Import downloaded files
|
||||||
context.Progress().InitBar(int64(len(queue)), false, aptly.BarMirrorUpdateImportFiles)
|
context.Progress().InitBar(int64(len(queue)), false, aptly.BarMirrorUpdateImportFiles)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -25,7 +25,7 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
|
|||||||
return aptlyPublishListTxt(cmd, args)
|
return aptlyPublishListTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyPublishListTxt(cmd *commander.Command, args []string) error {
|
func aptlyPublishListTxt(cmd *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||||
@@ -34,7 +34,7 @@ func aptlyPublishListTxt(cmd *commander.Command, args []string) error {
|
|||||||
published := make([]string, 0, collectionFactory.PublishedRepoCollection().Len())
|
published := make([]string, 0, collectionFactory.PublishedRepoCollection().Len())
|
||||||
|
|
||||||
err = collectionFactory.PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
err = collectionFactory.PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
||||||
e := collectionFactory.PublishedRepoCollection().LoadComplete(repo, collectionFactory)
|
e := collectionFactory.PublishedRepoCollection().LoadShallow(repo, collectionFactory)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error found on one publish (prefix:%s / distribution:%s / component:%s\n)",
|
fmt.Fprintf(os.Stderr, "Error found on one publish (prefix:%s / distribution:%s / component:%s\n)",
|
||||||
repo.StoragePrefix(), repo.Distribution, repo.Components())
|
repo.StoragePrefix(), repo.Distribution, repo.Components())
|
||||||
@@ -77,7 +77,7 @@ func aptlyPublishListTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyPublishListJSON(cmd *commander.Command, args []string) error {
|
func aptlyPublishListJSON(_ *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
repos := make([]*deb.PublishedRepo, 0, context.NewCollectionFactory().PublishedRepoCollection().Len())
|
repos := make([]*deb.PublishedRepo, 0, context.NewCollectionFactory().PublishedRepoCollection().Len())
|
||||||
|
|||||||
@@ -48,8 +48,10 @@ Example:
|
|||||||
cmd.Flag.String("butautomaticupgrades", "", "set value for ButAutomaticUpgrades field")
|
cmd.Flag.String("butautomaticupgrades", "", "set value for ButAutomaticUpgrades field")
|
||||||
cmd.Flag.String("label", "", "label to publish")
|
cmd.Flag.String("label", "", "label to publish")
|
||||||
cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)")
|
cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)")
|
||||||
|
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
|
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
|
||||||
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ func aptlyPublishShow(cmd *commander.Command, args []string) error {
|
|||||||
return aptlyPublishShowTxt(cmd, args)
|
return aptlyPublishShowTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyPublishShowTxt(cmd *commander.Command, args []string) error {
|
func aptlyPublishShowTxt(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
distribution := args[0]
|
distribution := args[0]
|
||||||
@@ -76,7 +76,7 @@ func aptlyPublishShowTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyPublishShowJSON(cmd *commander.Command, args []string) error {
|
func aptlyPublishShowJSON(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
distribution := args[0]
|
distribution := args[0]
|
||||||
|
|||||||
@@ -116,8 +116,9 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
|||||||
origin := context.Flags().Lookup("origin").Value.String()
|
origin := context.Flags().Lookup("origin").Value.String()
|
||||||
notAutomatic := context.Flags().Lookup("notautomatic").Value.String()
|
notAutomatic := context.Flags().Lookup("notautomatic").Value.String()
|
||||||
butAutomaticUpgrades := context.Flags().Lookup("butautomaticupgrades").Value.String()
|
butAutomaticUpgrades := context.Flags().Lookup("butautomaticupgrades").Value.String()
|
||||||
|
multiDist := context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
|
|
||||||
published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, collectionFactory)
|
published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, collectionFactory, multiDist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to publish: %s", err)
|
return fmt.Errorf("unable to publish: %s", err)
|
||||||
}
|
}
|
||||||
@@ -132,6 +133,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
published.Label = context.Flags().Lookup("label").Value.String()
|
published.Label = context.Flags().Lookup("label").Value.String()
|
||||||
published.Suite = context.Flags().Lookup("suite").Value.String()
|
published.Suite = context.Flags().Lookup("suite").Value.String()
|
||||||
|
published.Codename = context.Flags().Lookup("codename").Value.String()
|
||||||
|
|
||||||
published.SkipContents = context.Config().SkipContentsPublishing
|
published.SkipContents = context.Config().SkipContentsPublishing
|
||||||
|
|
||||||
@@ -161,8 +163,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||||
if forceOverwrite {
|
if forceOverwrite {
|
||||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing the same package pool.\n")
|
||||||
"the same package pool.\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite)
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite)
|
||||||
@@ -239,8 +240,10 @@ Example:
|
|||||||
cmd.Flag.String("butautomaticupgrades", "", "overwrite value for ButAutomaticUpgrades field")
|
cmd.Flag.String("butautomaticupgrades", "", "overwrite value for ButAutomaticUpgrades field")
|
||||||
cmd.Flag.String("label", "", "label to publish")
|
cmd.Flag.String("label", "", "label to publish")
|
||||||
cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)")
|
cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)")
|
||||||
|
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
|
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
|
||||||
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/deb"
|
"github.com/aptly-dev/aptly/deb"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
|
||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
"github.com/smira/flag"
|
"github.com/smira/flag"
|
||||||
)
|
)
|
||||||
@@ -64,10 +63,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, component := range components {
|
for i, component := range components {
|
||||||
if !utils.StrSliceHasItem(publishedComponents, component) {
|
|
||||||
return fmt.Errorf("unable to switch: component %s is not in published repository", component)
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot, err = collectionFactory.SnapshotCollection().ByName(names[i])
|
snapshot, err = collectionFactory.SnapshotCollection().ByName(names[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to switch: %s", err)
|
return fmt.Errorf("unable to switch: %s", err)
|
||||||
@@ -100,6 +95,10 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("multi-dist") {
|
||||||
|
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite)
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to publish: %s", err)
|
return fmt.Errorf("unable to publish: %s", err)
|
||||||
@@ -161,6 +160,7 @@ This command would switch published repository (with one component) named ppa/wh
|
|||||||
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
|
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
||||||
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("multi-dist") {
|
||||||
|
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite)
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to publish: %s", err)
|
return fmt.Errorf("unable to publish: %s", err)
|
||||||
@@ -119,6 +123,7 @@ Example:
|
|||||||
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
||||||
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -29,7 +29,10 @@ func aptlyRepoInclude(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
|
forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
|
||||||
acceptUnsigned := context.Flags().Lookup("accept-unsigned").Value.Get().(bool)
|
acceptUnsigned := context.Flags().Lookup("accept-unsigned").Value.Get().(bool)
|
||||||
ignoreSignatures := context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
|
if context.Flags().IsSet("ignore-signatures") {
|
||||||
|
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||||
|
}
|
||||||
noRemoveFiles := context.Flags().Lookup("no-remove-files").Value.Get().(bool)
|
noRemoveFiles := context.Flags().Lookup("no-remove-files").Value.Get().(bool)
|
||||||
repoTemplateString := context.Flags().Lookup("repo").Value.Get().(string)
|
repoTemplateString := context.Flags().Lookup("repo").Value.Get().(string)
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ func aptlyRepoList(cmd *commander.Command, args []string) error {
|
|||||||
return aptlyRepoListTxt(cmd, args)
|
return aptlyRepoListTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyRepoListTxt(cmd *commander.Command, args []string) error {
|
func aptlyRepoListTxt(cmd *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||||
@@ -71,7 +71,7 @@ func aptlyRepoListTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyRepoListJSON(cmd *commander.Command, args []string) error {
|
func aptlyRepoListJSON(_ *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
repos := make([]*deb.LocalRepo, context.NewCollectionFactory().LocalRepoCollection().Len())
|
repos := make([]*deb.LocalRepo, context.NewCollectionFactory().LocalRepoCollection().Len())
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error {
|
|||||||
return aptlyRepoShowTxt(cmd, args)
|
return aptlyRepoShowTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyRepoShowTxt(cmd *commander.Command, args []string) error {
|
func aptlyRepoShowTxt(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
@@ -58,7 +58,7 @@ func aptlyRepoShowTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlyRepoShowJSON(cmd *commander.Command, args []string) error {
|
func aptlyRepoShowJSON(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ func aptlyServe(cmd *commander.Command, args []string) error {
|
|||||||
// anything else must fail.
|
// anything else must fail.
|
||||||
// E.g.: Running the service under a different user may lead to a rootDir
|
// E.g.: Running the service under a different user may lead to a rootDir
|
||||||
// that exists but is not usable due to access permissions.
|
// that exists but is not usable due to access permissions.
|
||||||
err = utils.DirIsAccessible(context.Config().RootDir)
|
err = utils.DirIsAccessible(context.Config().GetRootDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error {
|
|||||||
return aptlySnapshotListTxt(cmd, args)
|
return aptlySnapshotListTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlySnapshotListTxt(cmd *commander.Command, args []string) error {
|
func aptlySnapshotListTxt(cmd *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||||
@@ -59,7 +59,7 @@ func aptlySnapshotListTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlySnapshotListJSON(cmd *commander.Command, args []string) error {
|
func aptlySnapshotListJSON(cmd *commander.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string)
|
sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error {
|
|||||||
return aptlySnapshotShowTxt(cmd, args)
|
return aptlySnapshotShowTxt(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlySnapshotShowTxt(cmd *commander.Command, args []string) error {
|
func aptlySnapshotShowTxt(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
name := args[0]
|
name := args[0]
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
@@ -85,7 +85,7 @@ func aptlySnapshotShowTxt(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aptlySnapshotShowJSON(cmd *commander.Command, args []string) error {
|
func aptlySnapshotShowJSON(_ *commander.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|||||||
+3
-4
@@ -62,11 +62,10 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error {
|
|||||||
text, _ = reader.ReadString('\n')
|
text, _ = reader.ReadString('\n')
|
||||||
if text == "\n" {
|
if text == "\n" {
|
||||||
break
|
break
|
||||||
} else {
|
|
||||||
text = strings.TrimSpace(text) + ","
|
|
||||||
parsedArgs, _ := shellwords.Parse(text)
|
|
||||||
cmdArgs = append(cmdArgs, parsedArgs...)
|
|
||||||
}
|
}
|
||||||
|
text = strings.TrimSpace(text) + ","
|
||||||
|
parsedArgs, _ := shellwords.Parse(text)
|
||||||
|
cmdArgs = append(cmdArgs, parsedArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cmdArgs) == 0 {
|
if len(cmdArgs) == 0 {
|
||||||
|
|||||||
+1
-1
@@ -3,5 +3,5 @@ coverage:
|
|||||||
project:
|
project:
|
||||||
default:
|
default:
|
||||||
target: auto
|
target: auto
|
||||||
threshold: 0%
|
threshold: 2%
|
||||||
if_ci_failed: error
|
if_ci_failed: error
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
"-distribution=[distribution name to publish]:distribution:($dists)"
|
"-distribution=[distribution name to publish]:distribution:($dists)"
|
||||||
"-label=[label to publish]:label: "
|
"-label=[label to publish]:label: "
|
||||||
"-suite=[suite to publish]:suite: "
|
"-suite=[suite to publish]:suite: "
|
||||||
|
"-codename=[codename to publish]:codename: "
|
||||||
"-notautomatic=[set value for NotAutomatic field]:notautomatic: "
|
"-notautomatic=[set value for NotAutomatic field]:notautomatic: "
|
||||||
"-origin=[origin name to publish]:origin: "
|
"-origin=[origin name to publish]:origin: "
|
||||||
${components_options[@]}
|
${components_options[@]}
|
||||||
|
|||||||
+1
-3
@@ -1,5 +1,3 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# (The MIT License)
|
# (The MIT License)
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Andrey Smirnov
|
# Copyright (c) 2014 Andrey Smirnov
|
||||||
@@ -503,7 +501,7 @@ _aptly()
|
|||||||
"snapshot"|"repo")
|
"snapshot"|"repo")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-acquire-by-hash -batch -butautomaticupgrades= -component= -distribution= -force-overwrite -gpg-key= -keyring= -label= -suite= -notautomatic= -origin= -passphrase= -passphrase-file= -secret-keyring= -skip-contents -skip-bz2 -skip-signing" -- ${cur}))
|
COMPREPLY=($(compgen -W "-acquire-by-hash -batch -butautomaticupgrades= -component= -distribution= -force-overwrite -gpg-key= -keyring= -label= -suite= -codename= -notautomatic= -origin= -passphrase= -passphrase-file= -secret-keyring= -skip-contents -skip-bz2 -skip-signing -multi-dist" -- ${cur}))
|
||||||
else
|
else
|
||||||
if [[ "$subcmd" == "snapshot" ]]; then
|
if [[ "$subcmd" == "snapshot" ]]; then
|
||||||
COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur}))
|
COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur}))
|
||||||
|
|||||||
+71
-14
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/cheggaaa/pb"
|
"github.com/cheggaaa/pb"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/wsxiaoys/terminal/color"
|
"github.com/wsxiaoys/terminal/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ type Progress struct {
|
|||||||
queue chan printTask
|
queue chan printTask
|
||||||
bar *pb.ProgressBar
|
bar *pb.ProgressBar
|
||||||
barShown bool
|
barShown bool
|
||||||
|
worker ProgressWorker
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check interface
|
// Check interface
|
||||||
@@ -42,16 +44,19 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewProgress creates new progress instance
|
// NewProgress creates new progress instance
|
||||||
func NewProgress() *Progress {
|
func NewProgress(structuredLogging bool) *Progress {
|
||||||
return &Progress{
|
p := &Progress{
|
||||||
stopped: make(chan bool),
|
stopped: make(chan bool),
|
||||||
queue: make(chan printTask, 100),
|
queue: make(chan printTask, 100),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.worker = progressWorkerFactroy(structuredLogging, p)
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start makes progress start its work
|
// Start makes progress start its work
|
||||||
func (p *Progress) Start() {
|
func (p *Progress) Start() {
|
||||||
go p.worker()
|
go p.worker.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown shuts down progress display
|
// Shutdown shuts down progress display
|
||||||
@@ -69,7 +74,7 @@ func (p *Progress) Flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitBar starts progressbar for count bytes or count items
|
// InitBar starts progressbar for count bytes or count items
|
||||||
func (p *Progress) InitBar(count int64, isBytes bool, barType aptly.BarType) {
|
func (p *Progress) InitBar(count int64, isBytes bool, _ aptly.BarType) {
|
||||||
if p.bar != nil {
|
if p.bar != nil {
|
||||||
panic("bar already initialized")
|
panic("bar already initialized")
|
||||||
}
|
}
|
||||||
@@ -173,42 +178,94 @@ func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Progress) worker() {
|
type ProgressWorker interface {
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func progressWorkerFactroy(structuredLogging bool, progress *Progress) ProgressWorker {
|
||||||
|
if structuredLogging {
|
||||||
|
worker := loggerProgressWorker{progress: progress}
|
||||||
|
return &worker
|
||||||
|
}
|
||||||
|
|
||||||
|
worker := standardProgressWorker{progress: progress}
|
||||||
|
return &worker
|
||||||
|
}
|
||||||
|
|
||||||
|
type standardProgressWorker struct {
|
||||||
|
progress *Progress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *standardProgressWorker) run() {
|
||||||
hasBar := false
|
hasBar := false
|
||||||
|
|
||||||
for {
|
for {
|
||||||
task := <-p.queue
|
task := <-w.progress.queue
|
||||||
switch task.code {
|
switch task.code {
|
||||||
case codeBarEnabled:
|
case codeBarEnabled:
|
||||||
hasBar = true
|
hasBar = true
|
||||||
case codeBarDisabled:
|
case codeBarDisabled:
|
||||||
hasBar = false
|
hasBar = false
|
||||||
case codePrint:
|
case codePrint:
|
||||||
if p.barShown {
|
if w.progress.barShown {
|
||||||
fmt.Print("\r\033[2K")
|
fmt.Print("\r\033[2K")
|
||||||
p.barShown = false
|
w.progress.barShown = false
|
||||||
}
|
}
|
||||||
fmt.Print(task.message)
|
fmt.Print(task.message)
|
||||||
case codePrintStdErr:
|
case codePrintStdErr:
|
||||||
if p.barShown {
|
if w.progress.barShown {
|
||||||
fmt.Print("\r\033[2K")
|
fmt.Print("\r\033[2K")
|
||||||
p.barShown = false
|
w.progress.barShown = false
|
||||||
}
|
}
|
||||||
fmt.Fprint(os.Stderr, task.message)
|
fmt.Fprint(os.Stderr, task.message)
|
||||||
case codeProgress:
|
case codeProgress:
|
||||||
if hasBar {
|
if hasBar {
|
||||||
fmt.Print("\r" + task.message)
|
fmt.Print("\r" + task.message)
|
||||||
p.barShown = true
|
w.progress.barShown = true
|
||||||
}
|
}
|
||||||
case codeHideProgress:
|
case codeHideProgress:
|
||||||
if p.barShown {
|
if w.progress.barShown {
|
||||||
fmt.Print("\r\033[2K")
|
fmt.Print("\r\033[2K")
|
||||||
p.barShown = false
|
w.progress.barShown = false
|
||||||
}
|
}
|
||||||
case codeFlush:
|
case codeFlush:
|
||||||
task.reply <- true
|
task.reply <- true
|
||||||
case codeStop:
|
case codeStop:
|
||||||
p.stopped <- true
|
w.progress.stopped <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerProgressWorker struct {
|
||||||
|
progress *Progress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *loggerProgressWorker) run() {
|
||||||
|
hasBar := false
|
||||||
|
|
||||||
|
for {
|
||||||
|
task := <-w.progress.queue
|
||||||
|
switch task.code {
|
||||||
|
case codeBarEnabled:
|
||||||
|
hasBar = true
|
||||||
|
case codeBarDisabled:
|
||||||
|
hasBar = false
|
||||||
|
case codePrint, codePrintStdErr:
|
||||||
|
log.Info().Msg(strings.TrimSuffix(task.message, "\n"))
|
||||||
|
case codeProgress:
|
||||||
|
if hasBar {
|
||||||
|
log.Info().Msg(strings.TrimSuffix(task.message, "\n"))
|
||||||
|
w.progress.barShown = true
|
||||||
|
}
|
||||||
|
case codeHideProgress:
|
||||||
|
if w.progress.barShown {
|
||||||
|
w.progress.barShown = false
|
||||||
|
}
|
||||||
|
case codeFlush:
|
||||||
|
task.reply <- true
|
||||||
|
case codeStop:
|
||||||
|
w.progress.stopped <- true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
TestingT(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgressSuite struct {}
|
||||||
|
|
||||||
|
var _ = Suite(&ProgressSuite{})
|
||||||
|
|
||||||
|
func (s *ProgressSuite) TestNewProgress(c *C) {
|
||||||
|
p := NewProgress(false)
|
||||||
|
c.Check(fmt.Sprintf("%T", p.worker), Equals, fmt.Sprintf("%T", &standardProgressWorker{}))
|
||||||
|
|
||||||
|
p = NewProgress(true)
|
||||||
|
c.Check(fmt.Sprintf("%T", p.worker), Equals, fmt.Sprintf("%T", &loggerProgressWorker{}))
|
||||||
|
}
|
||||||
+58
-16
@@ -3,6 +3,7 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
gocontext "context"
|
gocontext "context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
"github.com/aptly-dev/aptly/azure"
|
"github.com/aptly-dev/aptly/azure"
|
||||||
"github.com/aptly-dev/aptly/console"
|
"github.com/aptly-dev/aptly/console"
|
||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
"github.com/aptly-dev/aptly/database/etcddb"
|
||||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||||
"github.com/aptly-dev/aptly/deb"
|
"github.com/aptly-dev/aptly/deb"
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
@@ -48,6 +50,7 @@ type AptlyContext struct {
|
|||||||
publishedStorages map[string]aptly.PublishedStorage
|
publishedStorages map[string]aptly.PublishedStorage
|
||||||
dependencyOptions int
|
dependencyOptions int
|
||||||
architecturesList []string
|
architecturesList []string
|
||||||
|
structuredLogging bool
|
||||||
// Debug features
|
// Debug features
|
||||||
fileCPUProfile *os.File
|
fileCPUProfile *os.File
|
||||||
fileMemProfile *os.File
|
fileMemProfile *os.File
|
||||||
@@ -93,13 +96,15 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
|||||||
Fatal(err)
|
Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
configLocations := []string{
|
homeLocation := filepath.Join(os.Getenv("HOME"), ".aptly.conf")
|
||||||
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
|
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
|
||||||
"/etc/aptly.conf",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, configLocation := range configLocations {
|
for _, configLocation := range configLocations {
|
||||||
|
// FIXME: check if exists, check if readable
|
||||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||||
|
if os.IsPermission(err) || os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -109,7 +114,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", configLocations[0])
|
fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", homeLocation)
|
||||||
|
|
||||||
// as this is fresh aptly installation, we don't need to support legacy pool locations
|
// as this is fresh aptly installation, we don't need to support legacy pool locations
|
||||||
utils.Config.SkipLegacyPool = true
|
utils.Config.SkipLegacyPool = true
|
||||||
@@ -195,7 +200,7 @@ func (context *AptlyContext) Progress() aptly.Progress {
|
|||||||
|
|
||||||
func (context *AptlyContext) _progress() aptly.Progress {
|
func (context *AptlyContext) _progress() aptly.Progress {
|
||||||
if context.progress == nil {
|
if context.progress == nil {
|
||||||
context.progress = console.NewProgress()
|
context.progress = console.NewProgress(context.structuredLogging)
|
||||||
context.progress.Start()
|
context.progress.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +277,7 @@ func (context *AptlyContext) DBPath() string {
|
|||||||
|
|
||||||
// DBPath builds path to database
|
// DBPath builds path to database
|
||||||
func (context *AptlyContext) dbPath() string {
|
func (context *AptlyContext) dbPath() string {
|
||||||
return filepath.Join(context.config().RootDir, "db")
|
return filepath.Join(context.config().GetRootDir(), "db")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database opens and returns current instance of database
|
// Database opens and returns current instance of database
|
||||||
@@ -286,8 +291,18 @@ func (context *AptlyContext) Database() (database.Storage, error) {
|
|||||||
func (context *AptlyContext) _database() (database.Storage, error) {
|
func (context *AptlyContext) _database() (database.Storage, error) {
|
||||||
if context.database == nil {
|
if context.database == nil {
|
||||||
var err error
|
var err error
|
||||||
|
switch context.config().DatabaseBackend.Type {
|
||||||
context.database, err = goleveldb.NewDB(context.dbPath())
|
case "leveldb":
|
||||||
|
if len(context.config().DatabaseBackend.DbPath) == 0 {
|
||||||
|
return nil, errors.New("leveldb databaseBackend config invalid")
|
||||||
|
}
|
||||||
|
dbPath := filepath.Join(context.config().RootDir, context.config().DatabaseBackend.DbPath)
|
||||||
|
context.database, err = goleveldb.NewDB(dbPath)
|
||||||
|
case "etcd":
|
||||||
|
context.database, err = etcddb.NewDB(context.config().DatabaseBackend.URL)
|
||||||
|
default:
|
||||||
|
context.database, err = goleveldb.NewDB(context.dbPath())
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't instantiate database: %s", err)
|
return nil, fmt.Errorf("can't instantiate database: %s", err)
|
||||||
}
|
}
|
||||||
@@ -360,7 +375,26 @@ func (context *AptlyContext) PackagePool() aptly.PackagePool {
|
|||||||
defer context.Unlock()
|
defer context.Unlock()
|
||||||
|
|
||||||
if context.packagePool == nil {
|
if context.packagePool == nil {
|
||||||
context.packagePool = files.NewPackagePool(context.config().RootDir, !context.config().SkipLegacyPool)
|
storageConfig := context.config().PackagePoolStorage
|
||||||
|
if storageConfig.Azure != nil {
|
||||||
|
var err error
|
||||||
|
context.packagePool, err = azure.NewPackagePool(
|
||||||
|
storageConfig.Azure.AccountName,
|
||||||
|
storageConfig.Azure.AccountKey,
|
||||||
|
storageConfig.Azure.Container,
|
||||||
|
storageConfig.Azure.Prefix,
|
||||||
|
storageConfig.Azure.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
poolRoot := context.config().PackagePoolStorage.Local.Path
|
||||||
|
if poolRoot == "" {
|
||||||
|
poolRoot = filepath.Join(context.config().RootDir, "pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
context.packagePool = files.NewPackagePool(poolRoot, !context.config().SkipLegacyPool)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.packagePool
|
return context.packagePool
|
||||||
@@ -374,7 +408,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto
|
|||||||
publishedStorage, ok := context.publishedStorages[name]
|
publishedStorage, ok := context.publishedStorages[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
publishedStorage = files.NewPublishedStorage(filepath.Join(context.config().RootDir, "public"), "hardlink", "")
|
publishedStorage = files.NewPublishedStorage(filepath.Join(context.config().GetRootDir(), "public"), "hardlink", "")
|
||||||
} else if strings.HasPrefix(name, "filesystem:") {
|
} else if strings.HasPrefix(name, "filesystem:") {
|
||||||
params, ok := context.config().FileSystemPublishRoots[name[11:]]
|
params, ok := context.config().FileSystemPublishRoots[name[11:]]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -393,7 +427,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto
|
|||||||
params.AccessKeyID, params.SecretAccessKey, params.SessionToken,
|
params.AccessKeyID, params.SecretAccessKey, params.SessionToken,
|
||||||
params.Region, params.Endpoint, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
|
params.Region, params.Endpoint, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
|
||||||
params.EncryptionMethod, params.PlusWorkaround, params.DisableMultiDel,
|
params.EncryptionMethod, params.PlusWorkaround, params.DisableMultiDel,
|
||||||
params.ForceSigV2, params.Debug)
|
params.ForceSigV2, params.ForceVirtualHostedStyle, params.Debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatal(err)
|
Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -432,7 +466,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto
|
|||||||
|
|
||||||
// UploadPath builds path to upload storage
|
// UploadPath builds path to upload storage
|
||||||
func (context *AptlyContext) UploadPath() string {
|
func (context *AptlyContext) UploadPath() string {
|
||||||
return filepath.Join(context.Config().RootDir, "upload")
|
return filepath.Join(context.Config().GetRootDir(), "upload")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (context *AptlyContext) pgpProvider() string {
|
func (context *AptlyContext) pgpProvider() string {
|
||||||
@@ -456,7 +490,7 @@ func (context *AptlyContext) pgpProvider() string {
|
|||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (context *AptlyContext) getGPGFinder(provider string) pgp.GPGFinder {
|
func (context *AptlyContext) getGPGFinder() pgp.GPGFinder {
|
||||||
switch context.pgpProvider() {
|
switch context.pgpProvider() {
|
||||||
case "gpg1":
|
case "gpg1":
|
||||||
return pgp.GPG1Finder()
|
return pgp.GPG1Finder()
|
||||||
@@ -479,7 +513,7 @@ func (context *AptlyContext) GetSigner() pgp.Signer {
|
|||||||
return &pgp.GoSigner{}
|
return &pgp.GoSigner{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pgp.NewGpgSigner(context.getGPGFinder(provider))
|
return pgp.NewGpgSigner(context.getGPGFinder())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerifier returns Verifier with respect to provider
|
// GetVerifier returns Verifier with respect to provider
|
||||||
@@ -492,7 +526,7 @@ func (context *AptlyContext) GetVerifier() pgp.Verifier {
|
|||||||
return &pgp.GoVerifier{}
|
return &pgp.GoVerifier{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pgp.NewGpgVerifier(context.getGPGFinder(provider))
|
return pgp.NewGpgVerifier(context.getGPGFinder())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateFlags sets internal copy of flags in the context
|
// UpdateFlags sets internal copy of flags in the context
|
||||||
@@ -540,6 +574,11 @@ func (context *AptlyContext) GoContextHandleSignals() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StructuredLogging allows to set the structuredLogging flag
|
||||||
|
func (context *AptlyContext) StructuredLogging(structuredLogging bool) {
|
||||||
|
context.structuredLogging = structuredLogging
|
||||||
|
}
|
||||||
|
|
||||||
// Shutdown shuts context down
|
// Shutdown shuts context down
|
||||||
func (context *AptlyContext) Shutdown() {
|
func (context *AptlyContext) Shutdown() {
|
||||||
context.Lock()
|
context.Lock()
|
||||||
@@ -561,6 +600,9 @@ func (context *AptlyContext) Shutdown() {
|
|||||||
context.fileMemProfile = nil
|
context.fileMemProfile = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if context.taskList != nil {
|
||||||
|
context.taskList.Stop()
|
||||||
|
}
|
||||||
if context.database != nil {
|
if context.database != nil {
|
||||||
context.database.Close()
|
context.database.Close()
|
||||||
context.database = nil
|
context.database = nil
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package etcddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EtcDBatch struct {
|
||||||
|
s *EtcDStorage
|
||||||
|
ops []clientv3.Op
|
||||||
|
}
|
||||||
|
|
||||||
|
type WriteOptions struct {
|
||||||
|
NoWriteMerge bool
|
||||||
|
Sync bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EtcDBatch) Put(key []byte, value []byte) (err error) {
|
||||||
|
b.ops = append(b.ops, clientv3.OpPut(string(key), string(value)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EtcDBatch) Delete(key []byte) (err error) {
|
||||||
|
b.ops = append(b.ops, clientv3.OpDelete(string(key)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EtcDBatch) Write() (err error) {
|
||||||
|
kv := clientv3.NewKV(b.s.db)
|
||||||
|
|
||||||
|
batchSize := 128
|
||||||
|
for i := 0; i < len(b.ops); i += batchSize {
|
||||||
|
txn := kv.Txn(Ctx)
|
||||||
|
end := i + batchSize
|
||||||
|
if end > len(b.ops) {
|
||||||
|
end = len(b.ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := b.ops[i:end]
|
||||||
|
txn.Then(batch...)
|
||||||
|
_, err = txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// batch should implement database.Batch
|
||||||
|
var (
|
||||||
|
_ database.Batch = &EtcDBatch{}
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package etcddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Ctx = context.TODO()
|
||||||
|
|
||||||
|
func internalOpen(url string) (cli *clientv3.Client, err error) {
|
||||||
|
cfg := clientv3.Config{
|
||||||
|
Endpoints: []string{url},
|
||||||
|
DialTimeout: 30 * time.Second,
|
||||||
|
MaxCallSendMsgSize: 2147483647, // (2048 * 1024 * 1024) - 1
|
||||||
|
MaxCallRecvMsgSize: 2147483647,
|
||||||
|
DialKeepAliveTimeout: 7200 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
cli, err = clientv3.New(cfg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDB(url string) (database.Storage, error) {
|
||||||
|
cli, err := internalOpen(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &EtcDStorage{url, cli, ""}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package etcddb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
"github.com/aptly-dev/aptly/database/etcddb"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Launch gocheck tests
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
TestingT(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EtcDDBSuite struct {
|
||||||
|
url string
|
||||||
|
db database.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&EtcDDBSuite{})
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) SetUpTest(c *C) {
|
||||||
|
var err error
|
||||||
|
s.db, err = etcddb.NewDB("127.0.0.1:2379")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) TestSetUpTest(c *C) {
|
||||||
|
var err error
|
||||||
|
s.db, err = etcddb.NewDB("127.0.0.1:2379")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) TestGetPut(c *C) {
|
||||||
|
var (
|
||||||
|
key = []byte("key")
|
||||||
|
value = []byte("value")
|
||||||
|
)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
err = s.db.Put(key, value)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
result, err := s.db.Get(key)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(result, DeepEquals, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) TestDelete(c *C) {
|
||||||
|
var (
|
||||||
|
key = []byte("key")
|
||||||
|
value = []byte("value")
|
||||||
|
)
|
||||||
|
|
||||||
|
err := s.db.Put(key, value)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
_, err = s.db.Get(key)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
err = s.db.Delete(key)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) TestByPrefix(c *C) {
|
||||||
|
//c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
||||||
|
|
||||||
|
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
|
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||||
|
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||||
|
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||||
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
|
s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
||||||
|
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||||
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
|
s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
||||||
|
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||||
|
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
|
keys := [][]byte{}
|
||||||
|
values := [][]byte{}
|
||||||
|
|
||||||
|
c.Check(s.db.ProcessByPrefix([]byte{0x80}, func(k, v []byte) error {
|
||||||
|
keys = append(keys, append([]byte(nil), k...))
|
||||||
|
values = append(values, append([]byte(nil), v...))
|
||||||
|
return nil
|
||||||
|
}), IsNil)
|
||||||
|
|
||||||
|
c.Check(values, DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||||
|
c.Check(keys, DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||||
|
|
||||||
|
c.Check(s.db.ProcessByPrefix([]byte{0x80}, func(k, v []byte) error {
|
||||||
|
return database.ErrNotFound
|
||||||
|
}), Equals, database.ErrNotFound)
|
||||||
|
|
||||||
|
c.Check(s.db.ProcessByPrefix([]byte{0xa0}, func(k, v []byte) error {
|
||||||
|
return database.ErrNotFound
|
||||||
|
}), IsNil)
|
||||||
|
|
||||||
|
c.Check(s.db.FetchByPrefix([]byte{0xa0}), DeepEquals, [][]byte{})
|
||||||
|
c.Check(s.db.KeysByPrefix([]byte{0xa0}), DeepEquals, [][]byte{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) TestHasPrefix(c *C) {
|
||||||
|
//c.Check(s.db.HasPrefix([]byte(nil)), Equals, false)
|
||||||
|
//c.Check(s.db.HasPrefix([]byte{0x80}), Equals, false)
|
||||||
|
|
||||||
|
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||||
|
|
||||||
|
c.Check(s.db.HasPrefix([]byte(nil)), Equals, true)
|
||||||
|
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true)
|
||||||
|
c.Check(s.db.HasPrefix([]byte{0x79}), Equals, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
|
||||||
|
var (
|
||||||
|
key = []byte("key")
|
||||||
|
key2 = []byte("key2")
|
||||||
|
value = []byte("value")
|
||||||
|
value2 = []byte("value2")
|
||||||
|
)
|
||||||
|
transaction, err := s.db.OpenTransaction()
|
||||||
|
|
||||||
|
err = s.db.Put(key, value)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
transaction.Put(key2, value2)
|
||||||
|
v, err := s.db.Get(key)
|
||||||
|
c.Check(v, DeepEquals, value)
|
||||||
|
err = transaction.Delete(key)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
_, err = transaction.Get(key2)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
v2, err := transaction.Get(key2)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(v2, DeepEquals, value2)
|
||||||
|
|
||||||
|
_, err = transaction.Get(key)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
err = transaction.Commit()
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
|
||||||
|
v2, err = transaction.Get(key2)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(v2, DeepEquals, value2)
|
||||||
|
|
||||||
|
_, err = transaction.Get(key)
|
||||||
|
c.Assert(err, NotNil)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package etcddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EtcDStorage struct {
|
||||||
|
url string
|
||||||
|
db *clientv3.Client
|
||||||
|
tmpPrefix string // prefix for temporary DBs
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTemporary creates new DB of the same type in temp dir
|
||||||
|
func (s *EtcDStorage) CreateTemporary() (database.Storage, error) {
|
||||||
|
tmp := uuid.NewRandom().String()
|
||||||
|
return &EtcDStorage{
|
||||||
|
url: s.url,
|
||||||
|
db: s.db,
|
||||||
|
tmpPrefix: tmp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcDStorage) applyPrefix(key []byte) []byte {
|
||||||
|
if len(s.tmpPrefix) != 0 {
|
||||||
|
return append([]byte(s.tmpPrefix+"/"), key...)
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key value from etcd
|
||||||
|
func (s *EtcDStorage) Get(key []byte) (value []byte, err error) {
|
||||||
|
realKey := s.applyPrefix(key)
|
||||||
|
getResp, err := s.db.Get(Ctx, string(realKey))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, kv := range getResp.Kvs {
|
||||||
|
value = kv.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(value) == 0 {
|
||||||
|
err = database.ErrNotFound
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put saves key to etcd, if key has the same value in DB already, it is not saved
|
||||||
|
func (s *EtcDStorage) Put(key []byte, value []byte) (err error) {
|
||||||
|
realKey := s.applyPrefix(key)
|
||||||
|
_, err = s.db.Put(Ctx, string(realKey), string(value))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes key from etcd
|
||||||
|
func (s *EtcDStorage) Delete(key []byte) (err error) {
|
||||||
|
realKey := s.applyPrefix(key)
|
||||||
|
_, err = s.db.Delete(Ctx, string(realKey))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeysByPrefix returns all keys that start with prefix
|
||||||
|
func (s *EtcDStorage) KeysByPrefix(prefix []byte) [][]byte {
|
||||||
|
realPrefix := s.applyPrefix(prefix)
|
||||||
|
result := make([][]byte, 0, 20)
|
||||||
|
getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, ev := range getResp.Kvs {
|
||||||
|
key := ev.Key
|
||||||
|
keyc := make([]byte, len(key))
|
||||||
|
copy(keyc, key)
|
||||||
|
result = append(result, key)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchByPrefix returns all values with keys that start with prefix
|
||||||
|
func (s *EtcDStorage) FetchByPrefix(prefix []byte) [][]byte {
|
||||||
|
realPrefix := s.applyPrefix(prefix)
|
||||||
|
result := make([][]byte, 0, 20)
|
||||||
|
getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, kv := range getResp.Kvs {
|
||||||
|
valc := make([]byte, len(kv.Value))
|
||||||
|
copy(valc, kv.Value)
|
||||||
|
result = append(result, kv.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPrefix checks whether it can find any key with given prefix and returns true if one exists
|
||||||
|
func (s *EtcDStorage) HasPrefix(prefix []byte) bool {
|
||||||
|
realPrefix := s.applyPrefix(prefix)
|
||||||
|
getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return getResp.Count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessByPrefix iterates through all entries where key starts with prefix and calls
|
||||||
|
// StorageProcessor on key value pair
|
||||||
|
func (s *EtcDStorage) ProcessByPrefix(prefix []byte, proc database.StorageProcessor) error {
|
||||||
|
realPrefix := s.applyPrefix(prefix)
|
||||||
|
getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range getResp.Kvs {
|
||||||
|
err := proc(kv.Key, kv.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close finishes etcd connect
|
||||||
|
func (s *EtcDStorage) Close() error {
|
||||||
|
// do not close temporary db
|
||||||
|
if len(s.tmpPrefix) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := s.db.Close()
|
||||||
|
s.db = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen tries to open (re-open) the database
|
||||||
|
func (s *EtcDStorage) Open() error {
|
||||||
|
if s.db != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
s.db, err = internalOpen(s.url)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBatch creates a Batch object
|
||||||
|
func (s *EtcDStorage) CreateBatch() database.Batch {
|
||||||
|
if s.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &EtcDBatch{
|
||||||
|
s: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTransaction creates new transaction.
|
||||||
|
func (s *EtcDStorage) OpenTransaction() (database.Transaction, error) {
|
||||||
|
tmpdb, err := s.CreateTemporary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &transaction{s: s, tmpdb: tmpdb}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompactDB does nothing for etcd
|
||||||
|
func (s *EtcDStorage) CompactDB() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop removes only temporary DBs with etcd (i.e. remove all prefixed keys)
|
||||||
|
func (s *EtcDStorage) Drop() error {
|
||||||
|
if len(s.tmpPrefix) != 0 {
|
||||||
|
getResp, err := s.db.Get(Ctx, s.tmpPrefix, clientv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, kv := range getResp.Kvs {
|
||||||
|
_, err = s.db.Delete(Ctx, string(kv.Key))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete tempdb entry: %s", kv.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface
|
||||||
|
var (
|
||||||
|
_ database.Storage = &EtcDStorage{}
|
||||||
|
)
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package etcddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type transaction struct {
|
||||||
|
s *EtcDStorage
|
||||||
|
tmpdb database.Storage
|
||||||
|
ops []clientv3.Op
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements database.Reader interface.
|
||||||
|
func (t *transaction) Get(key []byte) (value []byte, err error) {
|
||||||
|
value, err = t.tmpdb.Get(key)
|
||||||
|
// if not found, search main db
|
||||||
|
if err != nil {
|
||||||
|
value, err = t.s.Get(key)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements database.Writer interface.
|
||||||
|
func (t *transaction) Put(key, value []byte) (err error) {
|
||||||
|
err = t.tmpdb.Put(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ops = append(t.ops, clientv3.OpPut(string(key), string(value)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements database.Writer interface.
|
||||||
|
func (t *transaction) Delete(key []byte) (err error) {
|
||||||
|
err = t.tmpdb.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ops = append(t.ops, clientv3.OpDelete(string(key)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transaction) Commit() (err error) {
|
||||||
|
kv := clientv3.NewKV(t.s.db)
|
||||||
|
|
||||||
|
batchSize := 128
|
||||||
|
for i := 0; i < len(t.ops); i += batchSize {
|
||||||
|
txn := kv.Txn(Ctx)
|
||||||
|
end := i + batchSize
|
||||||
|
if end > len(t.ops) {
|
||||||
|
end = len(t.ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := t.ops[i:end]
|
||||||
|
txn.Then(batch...)
|
||||||
|
_, err = txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.ops = []clientv3.Op{}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard is safe to call after Commit(), it would be no-op
|
||||||
|
func (t *transaction) Discard() {
|
||||||
|
t.ops = []clientv3.Op{}
|
||||||
|
t.tmpdb.Drop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// transaction should implement database.Transaction
|
||||||
|
var _ database.Transaction = &transaction{}
|
||||||
@@ -3,7 +3,6 @@ package goleveldb
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
@@ -19,7 +18,7 @@ type storage struct {
|
|||||||
|
|
||||||
// CreateTemporary creates new DB of the same type in temp dir
|
// CreateTemporary creates new DB of the same type in temp dir
|
||||||
func (s *storage) CreateTemporary() (database.Storage, error) {
|
func (s *storage) CreateTemporary() (database.Storage, error) {
|
||||||
tempdir, err := ioutil.TempDir("", "aptly")
|
tempdir, err := os.MkdirTemp("", "aptly")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-8
@@ -4,16 +4,17 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/pgp"
|
"github.com/aptly-dev/aptly/pgp"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
"github.com/saracen/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Changes is a result of .changes file parsing
|
// Changes is a result of .changes file parsing
|
||||||
@@ -39,7 +40,7 @@ func NewChanges(path string) (*Changes, error) {
|
|||||||
ChangesName: filepath.Base(path),
|
ChangesName: filepath.Base(path),
|
||||||
}
|
}
|
||||||
|
|
||||||
c.TempDir, err = ioutil.TempDir(os.TempDir(), "aptly")
|
c.TempDir, err = os.MkdirTemp(os.TempDir(), "aptly")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -221,12 +222,12 @@ func (c *Changes) GetField(field string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MatchesDependency implements PackageLike interface
|
// MatchesDependency implements PackageLike interface
|
||||||
func (c *Changes) MatchesDependency(d Dependency) bool {
|
func (c *Changes) MatchesDependency(_ Dependency) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchesArchitecture implements PackageLike interface
|
// MatchesArchitecture implements PackageLike interface
|
||||||
func (c *Changes) MatchesArchitecture(arch string) bool {
|
func (c *Changes) MatchesArchitecture(_ string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +249,8 @@ func (c *Changes) GetArchitecture() string {
|
|||||||
|
|
||||||
// CollectChangesFiles walks filesystem collecting all .changes files
|
// CollectChangesFiles walks filesystem collecting all .changes files
|
||||||
func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (changesFiles, failedFiles []string) {
|
func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (changesFiles, failedFiles []string) {
|
||||||
|
changesFilesLock := &sync.Mutex{}
|
||||||
|
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
info, err2 := os.Stat(location)
|
info, err2 := os.Stat(location)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
@@ -256,15 +259,14 @@ func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (cha
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error {
|
err2 = walker.Walk(location, func(path string, info os.FileInfo) error {
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(info.Name(), ".changes") {
|
if strings.HasSuffix(info.Name(), ".changes") {
|
||||||
|
changesFilesLock.Lock()
|
||||||
|
defer changesFilesLock.Unlock()
|
||||||
changesFiles = append(changesFiles, path)
|
changesFiles = append(changesFiles, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ func (s *ChangesSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.checksumStorage = files.NewMockChecksumStorage()
|
s.checksumStorage = files.NewMockChecksumStorage()
|
||||||
s.packagePool = files.NewPackagePool(s.Dir, false)
|
s.packagePool = files.NewPackagePool(s.Dir, false)
|
||||||
s.progress = console.NewProgress()
|
s.progress = console.NewProgress(false)
|
||||||
s.progress.Start()
|
s.progress.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ func (index *ContentsIndex) WriteTo(w io.Writer) (int64, error) {
|
|||||||
currentPkgs [][]byte
|
currentPkgs [][]byte
|
||||||
)
|
)
|
||||||
|
|
||||||
err = index.db.ProcessByPrefix(index.prefix, func(key []byte, value []byte) error {
|
err = index.db.ProcessByPrefix(index.prefix, func(key []byte, _ []byte) error {
|
||||||
// cut prefix
|
// cut prefix
|
||||||
key = key[prefixLen:]
|
key = key[prefixLen:]
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -136,7 +136,7 @@ func isMultilineField(field string, isRelease bool) bool {
|
|||||||
|
|
||||||
// Write single field from Stanza to writer.
|
// Write single field from Stanza to writer.
|
||||||
//
|
//
|
||||||
//nolint: interfacer
|
// nolint: interfacer
|
||||||
func writeField(w *bufio.Writer, field, value string, isRelease bool) (err error) {
|
func writeField(w *bufio.Writer, field, value string, isRelease bool) (err error) {
|
||||||
if !isMultilineField(field, isRelease) {
|
if !isMultilineField(field, isRelease) {
|
||||||
_, err = w.WriteString(field + ": " + value + "\n")
|
_, err = w.WriteString(field + ": " + value + "\n")
|
||||||
|
|||||||
+2
-2
@@ -119,8 +119,8 @@ func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz
|
|||||||
"shape": "Mrecord",
|
"shape": "Mrecord",
|
||||||
"style": "filled",
|
"style": "filled",
|
||||||
"fillcolor": "darkolivegreen1",
|
"fillcolor": "darkolivegreen1",
|
||||||
"label": fmt.Sprintf("%sPublished %s/%s|comp: %s|arch: %s%s", labelStart,
|
"label": fmt.Sprintf("%sPublished %s|comp: %s|arch: %s%s", labelStart,
|
||||||
repo.Prefix, repo.Distribution, strings.Join(repo.Components(), " "),
|
repo.GetPath(), strings.Join(repo.Components(), " "),
|
||||||
strings.Join(repo.Architectures, ", "), labelEnd),
|
strings.Join(repo.Architectures, ", "), labelEnd),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -5,14 +5,19 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/pgp"
|
"github.com/aptly-dev/aptly/pgp"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
"github.com/saracen/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CollectPackageFiles walks filesystem collecting all candidates for package files
|
// CollectPackageFiles walks filesystem collecting all candidates for package files
|
||||||
func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (packageFiles, otherFiles, failedFiles []string) {
|
func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (packageFiles, otherFiles, failedFiles []string) {
|
||||||
|
packageFilesLock := &sync.Mutex{}
|
||||||
|
otherFilesLock := &sync.Mutex{}
|
||||||
|
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
info, err2 := os.Stat(location)
|
info, err2 := os.Stat(location)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
@@ -21,18 +26,19 @@ func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (pac
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error {
|
err2 = walker.Walk(location, func(path string, info os.FileInfo) error {
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
|
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
|
||||||
strings.HasSuffix(info.Name(), ".dsc") || strings.HasSuffix(info.Name(), ".ddeb") {
|
strings.HasSuffix(info.Name(), ".dsc") || strings.HasSuffix(info.Name(), ".ddeb") {
|
||||||
|
packageFilesLock.Lock()
|
||||||
|
defer packageFilesLock.Unlock()
|
||||||
packageFiles = append(packageFiles, path)
|
packageFiles = append(packageFiles, path)
|
||||||
} else if strings.HasSuffix(info.Name(), ".buildinfo") {
|
} else if strings.HasSuffix(info.Name(), ".buildinfo") {
|
||||||
|
otherFilesLock.Lock()
|
||||||
|
defer otherFilesLock.Unlock()
|
||||||
otherFiles = append(otherFiles, path)
|
otherFiles = append(otherFiles, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,13 +77,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b
|
|||||||
list.PrepareIndex()
|
list.PrepareIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction, err := collection.db.OpenTransaction()
|
checksumStorage := checksumStorageProvider(collection.db)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer transaction.Discard()
|
|
||||||
|
|
||||||
checksumStorage := checksumStorageProvider(transaction)
|
|
||||||
|
|
||||||
for _, file := range packageFiles {
|
for _, file := range packageFiles {
|
||||||
var (
|
var (
|
||||||
@@ -201,7 +201,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.UpdateInTransaction(p, transaction)
|
err = collection.Update(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reporter.Warning("Unable to save package %s: %s", p, err)
|
reporter.Warning("Unable to save package %s: %s", p, err)
|
||||||
failedFiles = append(failedFiles, file)
|
failedFiles = append(failedFiles, file)
|
||||||
@@ -227,6 +227,6 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b
|
|||||||
processedFiles = append(processedFiles, candidateProcessedFiles...)
|
processedFiles = append(processedFiles, candidateProcessedFiles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = transaction.Commit()
|
err = nil // reset error as only failed files are reported
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-7
@@ -143,19 +143,20 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if signer != nil {
|
if signer != nil {
|
||||||
|
gpgExt := ".gpg"
|
||||||
if file.detachedSign {
|
if file.detachedSign {
|
||||||
err = signer.DetachedSign(file.tempFilename, file.tempFilename+".gpg")
|
err = signer.DetachedSign(file.tempFilename, file.tempFilename+gpgExt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to detached sign file: %s", err)
|
return fmt.Errorf("unable to detached sign file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.parent.suffix != "" {
|
if file.parent.suffix != "" {
|
||||||
file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg")] =
|
file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+gpgExt)] =
|
||||||
filepath.Join(file.parent.basePath, file.relativePath+".gpg")
|
filepath.Join(file.parent.basePath, file.relativePath+gpgExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg"),
|
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+gpgExt),
|
||||||
file.tempFilename+".gpg")
|
file.tempFilename+gpgExt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to publish file: %s", err)
|
return fmt.Errorf("unable to publish file: %s", err)
|
||||||
}
|
}
|
||||||
@@ -248,7 +249,7 @@ func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (files *indexFiles) PackageIndex(component, arch string, udeb, installer bool) *indexFile {
|
func (files *indexFiles) PackageIndex(component, arch string, udeb bool, installer bool, distribution string) *indexFile {
|
||||||
if arch == ArchitectureSource {
|
if arch == ArchitectureSource {
|
||||||
udeb = false
|
udeb = false
|
||||||
}
|
}
|
||||||
@@ -263,7 +264,11 @@ func (files *indexFiles) PackageIndex(component, arch string, udeb, installer bo
|
|||||||
if udeb {
|
if udeb {
|
||||||
relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Packages")
|
relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Packages")
|
||||||
} else if installer {
|
} else if installer {
|
||||||
relativePath = filepath.Join(component, fmt.Sprintf("installer-%s", arch), "current", "images", "SHA256SUMS")
|
if distribution == aptly.DistributionFocal {
|
||||||
|
relativePath = filepath.Join(component, fmt.Sprintf("installer-%s", arch), "current", "legacy-images", "SHA256SUMS")
|
||||||
|
} else {
|
||||||
|
relativePath = filepath.Join(component, fmt.Sprintf("installer-%s", arch), "current", "images", "SHA256SUMS")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages")
|
relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages")
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-11
@@ -145,7 +145,7 @@ func (l *PackageList) Add(p *Package) error {
|
|||||||
l.packages[key] = p
|
l.packages[key] = p
|
||||||
|
|
||||||
if l.indexed {
|
if l.indexed {
|
||||||
for _, provides := range p.Provides {
|
for _, provides := range p.ProvidedPackages() {
|
||||||
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ func (l *PackageList) Append(pl *PackageList) error {
|
|||||||
func (l *PackageList) Remove(p *Package) {
|
func (l *PackageList) Remove(p *Package) {
|
||||||
delete(l.packages, l.keyFunc(p))
|
delete(l.packages, l.keyFunc(p))
|
||||||
if l.indexed {
|
if l.indexed {
|
||||||
for _, provides := range p.Provides {
|
for _, provides := range p.ProvidedPackages() {
|
||||||
for i, pkg := range l.providesIndex[provides] {
|
for i, pkg := range l.providesIndex[provides] {
|
||||||
if pkg.Equals(p) {
|
if pkg.Equals(p) {
|
||||||
// remove l.ProvidesIndex[provides][i] w/o preserving order
|
// remove l.ProvidesIndex[provides][i] w/o preserving order
|
||||||
@@ -351,7 +351,7 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so
|
|||||||
cache[hash] = satisfied
|
cache[hash] = satisfied
|
||||||
}
|
}
|
||||||
|
|
||||||
if !satisfied && !ok {
|
if !satisfied {
|
||||||
variantsMissing = append(variantsMissing, dep)
|
variantsMissing = append(variantsMissing, dep)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +366,8 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
missing = depSliceDeduplicate(missing)
|
||||||
|
|
||||||
if progress != nil {
|
if progress != nil {
|
||||||
progress.ShutdownBar()
|
progress.ShutdownBar()
|
||||||
}
|
}
|
||||||
@@ -417,7 +419,7 @@ func (l *PackageList) PrepareIndex() {
|
|||||||
l.packagesIndex[i] = p
|
l.packagesIndex[i] = p
|
||||||
i++
|
i++
|
||||||
|
|
||||||
for _, provides := range p.Provides {
|
for _, provides := range p.ProvidedPackages() {
|
||||||
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,21 +472,25 @@ func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*
|
|||||||
searchResults = append(searchResults, p)
|
searchResults = append(searchResults, p)
|
||||||
|
|
||||||
if !allMatches {
|
if !allMatches {
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
if dep.Relation == VersionDontCare {
|
providers, ok := l.providesIndex[dep.Pkg]
|
||||||
for _, p := range l.providesIndex[dep.Pkg] {
|
if !ok {
|
||||||
if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) {
|
return
|
||||||
|
}
|
||||||
|
for _, p := range providers {
|
||||||
|
if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) {
|
||||||
|
if p.MatchesDependency(dep) {
|
||||||
searchResults = append(searchResults, p)
|
searchResults = append(searchResults, p)
|
||||||
|
}
|
||||||
|
|
||||||
if !allMatches {
|
if !allMatches {
|
||||||
break
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,14 @@ func (s *PackageListSuite) TestSearch(c *C) {
|
|||||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[4], s.packages2[5]})
|
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[4], s.packages2[5]})
|
||||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]})
|
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]})
|
||||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true), IsNil)
|
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true), IsNil)
|
||||||
|
|
||||||
|
// Provides with version number
|
||||||
|
python3CFFIBackend := &Package{Name: "python3-cffi-backend", Version: "1.15.1-5+b1", Architecture: "amd64", Provides: []string{"python3-cffi-backend-api-9729", "python3-cffi-backend-api-max (= 10495)", "python3-cffi-backend-api-min (= 9729)"}}
|
||||||
|
err := s.il2.Add(python3CFFIBackend)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionGreaterOrEqual, Version: "9729"}, false), DeepEquals, []*Package{python3CFFIBackend})
|
||||||
|
c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionLess, Version: "9729"}, false), IsNil)
|
||||||
|
c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionDontCare}, false), DeepEquals, []*Package{python3CFFIBackend})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageListSuite) TestFilter(c *C) {
|
func (s *PackageListSuite) TestFilter(c *C) {
|
||||||
|
|||||||
+2
-2
@@ -116,7 +116,7 @@ func (collection *LocalRepoCollection) search(filter func(*LocalRepo) bool, uniq
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error {
|
collection.db.ProcessByPrefix([]byte("L"), func(_, blob []byte) error {
|
||||||
r := &LocalRepo{}
|
r := &LocalRepo{}
|
||||||
if err := r.Decode(blob); err != nil {
|
if err := r.Decode(blob); err != nil {
|
||||||
log.Printf("Error decoding local repo: %s\n", err)
|
log.Printf("Error decoding local repo: %s\n", err)
|
||||||
@@ -219,7 +219,7 @@ func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
|||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) error {
|
func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) error {
|
||||||
return collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error {
|
return collection.db.ProcessByPrefix([]byte("L"), func(_, blob []byte) error {
|
||||||
r := &LocalRepo{}
|
r := &LocalRepo{}
|
||||||
if err := r.Decode(blob); err != nil {
|
if err := r.Decode(blob); err != nil {
|
||||||
log.Printf("Error decoding repo: %s\n", err)
|
log.Printf("Error decoding repo: %s\n", err)
|
||||||
|
|||||||
+88
-14
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Package is single instance of Debian package
|
// Package is single instance of Debian package
|
||||||
@@ -189,7 +190,12 @@ func NewInstallerPackageFromControlFile(input Stanza, repo *RemoteRepo, componen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath := filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "images")
|
var relPath string
|
||||||
|
if repo.Distribution == aptly.DistributionFocal {
|
||||||
|
relPath = filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "legacy-images")
|
||||||
|
} else {
|
||||||
|
relPath = filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "images")
|
||||||
|
}
|
||||||
for i := range files {
|
for i := range files {
|
||||||
files[i].downloadPath = relPath
|
files[i].downloadPath = relPath
|
||||||
|
|
||||||
@@ -307,6 +313,23 @@ func (p *Package) GetField(name string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProvidedPackages returns just the package names of the provided packages (without version numbers such as
|
||||||
|
// `(= 1.2.3)`).
|
||||||
|
func (p *Package) ProvidedPackages() []string {
|
||||||
|
result := make([]string, len(p.Provides))
|
||||||
|
for i, provided := range p.Provides {
|
||||||
|
providedDep, err := ParseDependency(provided)
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen, but I included this, so it definitely has the old behavior in case there is no
|
||||||
|
// special syntax.
|
||||||
|
result[i] = provided
|
||||||
|
} else {
|
||||||
|
result[i] = providedDep.Pkg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// MatchesArchitecture checks whether packages matches specified architecture
|
// MatchesArchitecture checks whether packages matches specified architecture
|
||||||
func (p *Package) MatchesArchitecture(arch string) bool {
|
func (p *Package) MatchesArchitecture(arch string) bool {
|
||||||
if p.Architecture == ArchitectureAll && arch != ArchitectureSource {
|
if p.Architecture == ArchitectureAll && arch != ArchitectureSource {
|
||||||
@@ -316,24 +339,75 @@ func (p *Package) MatchesArchitecture(arch string) bool {
|
|||||||
return p.Architecture == arch
|
return p.Architecture == arch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JoinErrors(errs ...error) error {
|
||||||
|
var combinedErr error
|
||||||
|
for _, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
if combinedErr == nil {
|
||||||
|
combinedErr = err
|
||||||
|
} else {
|
||||||
|
combinedErr = fmt.Errorf("%w\n%v", combinedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return combinedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// providesDependency checks if the package `Provide:`s the dependency, assuming that the architecture matches.
|
||||||
|
// If the `Provides:` entry includes a version number, it will be considered when checking the dependency.
|
||||||
|
func (p *Package) providesDependency(dep Dependency) (bool, error) {
|
||||||
|
var errs []error // won't cause an allocation in case of no errors
|
||||||
|
for _, provided := range p.Provides {
|
||||||
|
providedDep, err := ParseDependency(provided)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
// The only relation allowed here is `=`.
|
||||||
|
// > The relations allowed are [...]. The exception is the Provides field, for which only = is allowed.
|
||||||
|
// > [...]
|
||||||
|
// > A Provides field may contain version numbers, and such a version number will be considered when
|
||||||
|
// > considering a dependency on or conflict with the virtual package name.
|
||||||
|
// -- https://www.debian.org/doc/debian-policy/ch-relationships.html
|
||||||
|
switch providedDep.Relation {
|
||||||
|
case VersionDontCare:
|
||||||
|
if providedDep.Pkg == dep.Pkg {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case VersionEqual:
|
||||||
|
providedVersion := providedDep.Version
|
||||||
|
if providedDep.Pkg == dep.Pkg && versionSatisfiesDependency(providedVersion, dep) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errs = append(errs, fmt.Errorf("unsupported relation in Provides: %s", providedDep.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, JoinErrors(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
// MatchesDependency checks whether package matches specified dependency
|
// MatchesDependency checks whether package matches specified dependency
|
||||||
func (p *Package) MatchesDependency(dep Dependency) bool {
|
func (p *Package) MatchesDependency(dep Dependency) bool {
|
||||||
if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) {
|
if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if dep.Relation == VersionDontCare {
|
providesDep, err := p.providesDependency(dep)
|
||||||
if utils.StrSliceHasItem(p.Provides, dep.Pkg) {
|
if err != nil {
|
||||||
return true
|
log.Warn().Err(err).Msg("Error while checking if package provides dependency")
|
||||||
}
|
}
|
||||||
return dep.Pkg == p.Name
|
if providesDep {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if dep.Pkg != p.Name {
|
if dep.Pkg != p.Name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
r := CompareVersions(p.Version, dep.Version)
|
return versionSatisfiesDependency(p.Version, dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionSatisfiesDependency(version string, dep Dependency) bool {
|
||||||
|
r := CompareVersions(version, dep.Version)
|
||||||
|
|
||||||
switch dep.Relation {
|
switch dep.Relation {
|
||||||
case VersionEqual:
|
case VersionEqual:
|
||||||
@@ -347,13 +421,15 @@ func (p *Package) MatchesDependency(dep Dependency) bool {
|
|||||||
case VersionGreaterOrEqual:
|
case VersionGreaterOrEqual:
|
||||||
return r >= 0
|
return r >= 0
|
||||||
case VersionPatternMatch:
|
case VersionPatternMatch:
|
||||||
matched, err := filepath.Match(dep.Version, p.Version)
|
matched, err := filepath.Match(dep.Version, version)
|
||||||
return err == nil && matched
|
return err == nil && matched
|
||||||
case VersionRegexp:
|
case VersionRegexp:
|
||||||
return dep.Regexp.FindStringIndex(p.Version) != nil
|
return dep.Regexp.FindStringIndex(version) != nil
|
||||||
|
case VersionDontCare:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown relation: %d", dep.Relation))
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unknown relation")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns package name
|
// GetName returns package name
|
||||||
@@ -621,9 +697,7 @@ func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packageP
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
publishedDirectory := filepath.Join(prefix, relPath)
|
err = publishedStorage.LinkFromPool(prefix, relPath, f.Filename, packagePool, sourcePoolPath, f.Checksums, force)
|
||||||
|
|
||||||
err = publishedStorage.LinkFromPool(publishedDirectory, f.Filename, packagePool, sourcePoolPath, f.Checksums, force)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search is not implemented
|
// Search is not implemented
|
||||||
func (collection *PackageCollection) Search(dep Dependency, allMatches bool) (searchResults []*Package) {
|
func (collection *PackageCollection) Search(_ Dependency, _ bool) (searchResults []*Package) {
|
||||||
panic("Not implemented")
|
panic("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -333,9 +333,12 @@ func (s *PackageSuite) TestMatchesDependency(c *C) {
|
|||||||
|
|
||||||
// Provides
|
// Provides
|
||||||
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, false)
|
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, false)
|
||||||
p.Provides = []string{"fun", "game"}
|
p.Provides = []string{"fun (= 42)", "game"}
|
||||||
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, true)
|
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, true)
|
||||||
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Architecture: "amd64", Relation: VersionDontCare}), Equals, false)
|
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Architecture: "amd64", Relation: VersionDontCare}), Equals, false)
|
||||||
|
c.Check(p.MatchesDependency(Dependency{Pkg: "fun", Relation: VersionDontCare}), Equals, true)
|
||||||
|
c.Check(p.MatchesDependency(Dependency{Pkg: "fun", Relation: VersionGreaterOrEqual, Version: "42"}), Equals, true)
|
||||||
|
c.Check(p.MatchesDependency(Dependency{Pkg: "fun", Relation: VersionLess, Version: "42"}), Equals, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageSuite) TestGetDependencies(c *C) {
|
func (s *PackageSuite) TestGetDependencies(c *C) {
|
||||||
|
|||||||
+133
-44
@@ -5,7 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -44,6 +43,7 @@ type PublishedRepo struct {
|
|||||||
ButAutomaticUpgrades string
|
ButAutomaticUpgrades string
|
||||||
Label string
|
Label string
|
||||||
Suite string
|
Suite string
|
||||||
|
Codename string
|
||||||
// Architectures is a list of all architectures published
|
// Architectures is a list of all architectures published
|
||||||
Architectures []string
|
Architectures []string
|
||||||
// SourceKind is "local"/"repo"
|
// SourceKind is "local"/"repo"
|
||||||
@@ -70,6 +70,9 @@ type PublishedRepo struct {
|
|||||||
|
|
||||||
// Provide index files per hash also
|
// Provide index files per hash also
|
||||||
AcquireByHash bool
|
AcquireByHash bool
|
||||||
|
|
||||||
|
// Support multiple distributions
|
||||||
|
MultiDist bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePrefix splits [storage:]prefix into components
|
// ParsePrefix splits [storage:]prefix into components
|
||||||
@@ -153,13 +156,14 @@ func walkUpTree(source interface{}, collectionFactory *CollectionFactory) (rootD
|
|||||||
// distribution and architectures are user-defined properties
|
// distribution and architectures are user-defined properties
|
||||||
// components & sources are lists of component to source mapping (*Snapshot or *LocalRepo)
|
// components & sources are lists of component to source mapping (*Snapshot or *LocalRepo)
|
||||||
func NewPublishedRepo(storage, prefix, distribution string, architectures []string,
|
func NewPublishedRepo(storage, prefix, distribution string, architectures []string,
|
||||||
components []string, sources []interface{}, collectionFactory *CollectionFactory) (*PublishedRepo, error) {
|
components []string, sources []interface{}, collectionFactory *CollectionFactory, multiDist bool) (*PublishedRepo, error) {
|
||||||
result := &PublishedRepo{
|
result := &PublishedRepo{
|
||||||
UUID: uuid.New(),
|
UUID: uuid.New(),
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Architectures: architectures,
|
Architectures: architectures,
|
||||||
Sources: make(map[string]string),
|
Sources: make(map[string]string),
|
||||||
sourceItems: make(map[string]repoSourceItem),
|
sourceItems: make(map[string]repoSourceItem),
|
||||||
|
MultiDist: multiDist,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sources) == 0 {
|
if len(sources) == 0 {
|
||||||
@@ -197,9 +201,6 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
|
|||||||
if distribution == "" || component == "" {
|
if distribution == "" || component == "" {
|
||||||
rootDistributions, rootComponents := walkUpTree(source, collectionFactory)
|
rootDistributions, rootComponents := walkUpTree(source, collectionFactory)
|
||||||
if distribution == "" {
|
if distribution == "" {
|
||||||
for i := range rootDistributions {
|
|
||||||
rootDistributions[i] = strings.Replace(rootDistributions[i], "/", "-", -1)
|
|
||||||
}
|
|
||||||
discoveredDistributions = append(discoveredDistributions, rootDistributions...)
|
discoveredDistributions = append(discoveredDistributions, rootDistributions...)
|
||||||
}
|
}
|
||||||
if component == "" {
|
if component == "" {
|
||||||
@@ -264,10 +265,6 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(distribution, "/") {
|
|
||||||
return nil, fmt.Errorf("invalid distribution %s, '/' is not allowed", distribution)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Distribution = distribution
|
result.Distribution = distribution
|
||||||
|
|
||||||
// only fields which are unique by all given snapshots are set on published
|
// only fields which are unique by all given snapshots are set on published
|
||||||
@@ -284,7 +281,7 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON requires object to be "loaded completely"
|
// MarshalJSON requires object to filled by "LoadShallow" or "LoadComplete"
|
||||||
func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
||||||
type sourceInfo struct {
|
type sourceInfo struct {
|
||||||
Component, Name string
|
Component, Name string
|
||||||
@@ -312,6 +309,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
|||||||
"Label": p.Label,
|
"Label": p.Label,
|
||||||
"Origin": p.Origin,
|
"Origin": p.Origin,
|
||||||
"Suite": p.Suite,
|
"Suite": p.Suite,
|
||||||
|
"Codename": p.Codename,
|
||||||
"NotAutomatic": p.NotAutomatic,
|
"NotAutomatic": p.NotAutomatic,
|
||||||
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
|
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
|
||||||
"Prefix": p.Prefix,
|
"Prefix": p.Prefix,
|
||||||
@@ -321,6 +319,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
|||||||
"Storage": p.Storage,
|
"Storage": p.Storage,
|
||||||
"SkipContents": p.SkipContents,
|
"SkipContents": p.SkipContents,
|
||||||
"AcquireByHash": p.AcquireByHash,
|
"AcquireByHash": p.AcquireByHash,
|
||||||
|
"MultiDist": p.MultiDist,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +365,10 @@ func (p *PublishedRepo) String() string {
|
|||||||
extras = append(extras, fmt.Sprintf("suite: %s", p.Suite))
|
extras = append(extras, fmt.Sprintf("suite: %s", p.Suite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.Codename != "" {
|
||||||
|
extras = append(extras, fmt.Sprintf("codename: %s", p.Codename))
|
||||||
|
}
|
||||||
|
|
||||||
extra = strings.Join(extras, ", ")
|
extra = strings.Join(extras, ", ")
|
||||||
|
|
||||||
if extra != "" {
|
if extra != "" {
|
||||||
@@ -418,6 +421,29 @@ func (p *PublishedRepo) Components() []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Components returns sorted list of published repo source names
|
||||||
|
func (p *PublishedRepo) SourceNames() []string {
|
||||||
|
var sources = []string{}
|
||||||
|
|
||||||
|
for _, component := range p.Components() {
|
||||||
|
var source string
|
||||||
|
|
||||||
|
item := p.sourceItems[component]
|
||||||
|
if item.snapshot != nil {
|
||||||
|
source = item.snapshot.Name
|
||||||
|
} else if item.localRepo != nil {
|
||||||
|
source = item.localRepo.Name
|
||||||
|
} else {
|
||||||
|
panic("no snapshot/localRepo")
|
||||||
|
}
|
||||||
|
|
||||||
|
sources = append(sources, fmt.Sprintf("%s:%s", source, component))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(sources)
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateLocalRepo updates content from local repo in component
|
// UpdateLocalRepo updates content from local repo in component
|
||||||
func (p *PublishedRepo) UpdateLocalRepo(component string) {
|
func (p *PublishedRepo) UpdateLocalRepo(component string) {
|
||||||
if p.SourceKind != SourceLocalRepo {
|
if p.SourceKind != SourceLocalRepo {
|
||||||
@@ -437,7 +463,10 @@ func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) {
|
|||||||
panic("not snapshot publish")
|
panic("not snapshot publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
item := p.sourceItems[component]
|
item, exists := p.sourceItems[component]
|
||||||
|
if !exists {
|
||||||
|
item = repoSourceItem{}
|
||||||
|
}
|
||||||
item.snapshot = snapshot
|
item.snapshot = snapshot
|
||||||
p.sourceItems[component] = item
|
p.sourceItems[component] = item
|
||||||
|
|
||||||
@@ -513,6 +542,14 @@ func (p *PublishedRepo) GetSuite() string {
|
|||||||
return p.Suite
|
return p.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCodename returns default or manual Codename:
|
||||||
|
func (p *PublishedRepo) GetCodename() string {
|
||||||
|
if p.Codename == "" {
|
||||||
|
return p.Distribution
|
||||||
|
}
|
||||||
|
return p.Codename
|
||||||
|
}
|
||||||
|
|
||||||
// Publish publishes snapshot (repository) contents, links package files, generates Packages & Release files, signs them
|
// Publish publishes snapshot (repository) contents, links package files, generates Packages & Release files, signs them
|
||||||
func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageProvider aptly.PublishedStorageProvider,
|
func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageProvider aptly.PublishedStorageProvider,
|
||||||
collectionFactory *CollectionFactory, signer pgp.Signer, progress aptly.Progress, forceOverwrite bool) error {
|
collectionFactory *CollectionFactory, signer pgp.Signer, progress aptly.Progress, forceOverwrite bool) error {
|
||||||
@@ -582,7 +619,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tempDir string
|
var tempDir string
|
||||||
tempDir, err = ioutil.TempDir(os.TempDir(), "aptly")
|
tempDir, err = os.MkdirTemp(os.TempDir(), "aptly")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -605,7 +642,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
|
|
||||||
// For all architectures, pregenerate packages/sources files
|
// For all architectures, pregenerate packages/sources files
|
||||||
for _, arch := range p.Architectures {
|
for _, arch := range p.Architectures {
|
||||||
indexes.PackageIndex(component, arch, false, false)
|
indexes.PackageIndex(component, arch, false, false, p.Distribution)
|
||||||
}
|
}
|
||||||
|
|
||||||
list.PrepareIndex()
|
list.PrepareIndex()
|
||||||
@@ -627,9 +664,18 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return err2
|
return err2
|
||||||
}
|
}
|
||||||
relPath = filepath.Join("pool", component, poolDir)
|
if p.MultiDist {
|
||||||
|
relPath = filepath.Join("pool", p.Distribution, component, poolDir)
|
||||||
|
} else {
|
||||||
|
relPath = filepath.Join("pool", component, poolDir)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
relPath = filepath.Join("dists", p.Distribution, component, fmt.Sprintf("%s-%s", pkg.Name, arch), "current", "images")
|
if p.Distribution == aptly.DistributionFocal {
|
||||||
|
relPath = filepath.Join("dists", p.Distribution, component, fmt.Sprintf("%s-%s", pkg.Name, arch), "current", "legacy-images")
|
||||||
|
} else {
|
||||||
|
relPath = filepath.Join("dists", p.Distribution, component, fmt.Sprintf("%s-%s", pkg.Name, arch), "current", "images")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, relPath, forceOverwrite)
|
err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, relPath, forceOverwrite)
|
||||||
@@ -667,7 +713,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb, pkg.IsInstaller).BufWriter()
|
bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb, pkg.IsInstaller, p.Distribution).BufWriter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -721,7 +767,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
|
|
||||||
// For all architectures, pregenerate .udeb indexes
|
// For all architectures, pregenerate .udeb indexes
|
||||||
for _, arch := range p.Architectures {
|
for _, arch := range p.Architectures {
|
||||||
indexes.PackageIndex(component, arch, true, false)
|
indexes.PackageIndex(component, arch, true, false, p.Distribution)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,6 +781,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
release["Origin"] = p.GetOrigin()
|
release["Origin"] = p.GetOrigin()
|
||||||
release["Label"] = p.GetLabel()
|
release["Label"] = p.GetLabel()
|
||||||
release["Suite"] = p.GetSuite()
|
release["Suite"] = p.GetSuite()
|
||||||
|
release["Codename"] = p.GetCodename()
|
||||||
if p.AcquireByHash {
|
if p.AcquireByHash {
|
||||||
release["Acquire-By-Hash"] = "yes"
|
release["Acquire-By-Hash"] = "yes"
|
||||||
}
|
}
|
||||||
@@ -793,7 +840,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
}
|
}
|
||||||
release["Label"] = p.GetLabel()
|
release["Label"] = p.GetLabel()
|
||||||
release["Suite"] = p.GetSuite()
|
release["Suite"] = p.GetSuite()
|
||||||
release["Codename"] = p.Distribution
|
release["Codename"] = p.GetCodename()
|
||||||
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
||||||
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
||||||
if p.AcquireByHash {
|
if p.AcquireByHash {
|
||||||
@@ -953,8 +1000,11 @@ func (collection *PublishedRepoCollection) Update(repo *PublishedRepo) error {
|
|||||||
return batch.Write()
|
return batch.Write()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadComplete loads additional information for remote repo
|
// LoadShallow loads basic information on the repo's sources
|
||||||
func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) {
|
//
|
||||||
|
// This does not *fully* load in the sources themselves and their packages.
|
||||||
|
// It's useful if you just want to use JSON serialization without loading in unnecessary things.
|
||||||
|
func (collection *PublishedRepoCollection) LoadShallow(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) {
|
||||||
repo.sourceItems = make(map[string]repoSourceItem)
|
repo.sourceItems = make(map[string]repoSourceItem)
|
||||||
|
|
||||||
if repo.SourceKind == SourceSnapshot {
|
if repo.SourceKind == SourceSnapshot {
|
||||||
@@ -965,10 +1015,6 @@ func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, col
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = collectionFactory.SnapshotCollection().LoadComplete(item.snapshot)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.sourceItems[component] = item
|
repo.sourceItems[component] = item
|
||||||
}
|
}
|
||||||
@@ -980,6 +1026,30 @@ func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, col
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.packageRefs = &PackageRefList{}
|
||||||
|
repo.sourceItems[component] = item
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("unknown SourceKind")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadComplete loads complete information on the sources of the repo *and* their packages
|
||||||
|
func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, collectionFactory *CollectionFactory) (err error) {
|
||||||
|
collection.LoadShallow(repo, collectionFactory)
|
||||||
|
|
||||||
|
if repo.SourceKind == SourceSnapshot {
|
||||||
|
for _, item := range repo.sourceItems {
|
||||||
|
err = collectionFactory.SnapshotCollection().LoadComplete(item.snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if repo.SourceKind == SourceLocalRepo {
|
||||||
|
for component, item := range repo.sourceItems {
|
||||||
err = collectionFactory.LocalRepoCollection().LoadComplete(item.localRepo)
|
err = collectionFactory.LocalRepoCollection().LoadComplete(item.localRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -998,13 +1068,10 @@ func (collection *PublishedRepoCollection) LoadComplete(repo *PublishedRepo, col
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item.packageRefs = &PackageRefList{}
|
|
||||||
err = item.packageRefs.Decode(encoded)
|
err = item.packageRefs.Decode(encoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.sourceItems[component] = item
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic("unknown SourceKind")
|
panic("unknown SourceKind")
|
||||||
@@ -1086,7 +1153,7 @@ func (collection *PublishedRepoCollection) ByLocalRepo(repo *LocalRepo) []*Publi
|
|||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
func (collection *PublishedRepoCollection) ForEach(handler func(*PublishedRepo) error) error {
|
func (collection *PublishedRepoCollection) ForEach(handler func(*PublishedRepo) error) error {
|
||||||
return collection.db.ProcessByPrefix([]byte("U"), func(key, blob []byte) error {
|
return collection.db.ProcessByPrefix([]byte("U"), func(_, blob []byte) error {
|
||||||
r := &PublishedRepo{}
|
r := &PublishedRepo{}
|
||||||
if err := r.Decode(blob); err != nil {
|
if err := r.Decode(blob); err != nil {
|
||||||
log.Printf("Error decoding published repo: %s\n", err)
|
log.Printf("Error decoding published repo: %s\n", err)
|
||||||
@@ -1104,18 +1171,10 @@ func (collection *PublishedRepoCollection) Len() int {
|
|||||||
return len(collection.list)
|
return len(collection.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
|
func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix string, components []string,
|
||||||
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix string, components []string,
|
collectionFactory *CollectionFactory, progress aptly.Progress) (map[string][]string, error) {
|
||||||
publishedStorage aptly.PublishedStorage, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
|
||||||
|
|
||||||
collection.loadList()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
referencedFiles := map[string][]string{}
|
referencedFiles := map[string][]string{}
|
||||||
|
processedComponentRefs := map[string]*PackageRefList{}
|
||||||
if progress != nil {
|
|
||||||
progress.Printf("Cleaning up prefix %#v components %s...\n", prefix, strings.Join(components, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range collection.list {
|
for _, r := range collection.list {
|
||||||
if r.Prefix == prefix {
|
if r.Prefix == prefix {
|
||||||
@@ -1134,16 +1193,28 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix st
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(r, collectionFactory)
|
if err := collection.LoadComplete(r, collectionFactory); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, component := range components {
|
for _, component := range components {
|
||||||
if utils.StrSliceHasItem(repoComponents, component) {
|
if utils.StrSliceHasItem(repoComponents, component) {
|
||||||
packageList, err := NewPackageListFromRefList(r.RefList(component), collectionFactory.PackageCollection(), progress)
|
unseenRefs := r.RefList(component)
|
||||||
|
processedRefs := processedComponentRefs[component]
|
||||||
|
if processedRefs != nil {
|
||||||
|
unseenRefs = unseenRefs.Subtract(processedRefs)
|
||||||
|
} else {
|
||||||
|
processedRefs = NewPackageRefList()
|
||||||
|
}
|
||||||
|
|
||||||
|
if unseenRefs.Len() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
processedComponentRefs[component] = processedRefs.Merge(unseenRefs, false, true)
|
||||||
|
|
||||||
|
packageList, err := NewPackageListFromRefList(unseenRefs, collectionFactory.PackageCollection(), progress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
packageList.ForEach(func(p *Package) error {
|
packageList.ForEach(func(p *Package) error {
|
||||||
@@ -1163,6 +1234,24 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return referencedFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
|
||||||
|
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix string, components []string,
|
||||||
|
publishedStorage aptly.PublishedStorage, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
||||||
|
|
||||||
|
collection.loadList()
|
||||||
|
|
||||||
|
if progress != nil {
|
||||||
|
progress.Printf("Cleaning up prefix %#v components %s...\n", prefix, strings.Join(components, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
referencedFiles, err := collection.listReferencedFilesByComponent(prefix, components, collectionFactory, progress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, component := range components {
|
for _, component := range components {
|
||||||
sort.Strings(referencedFiles[component])
|
sort.Strings(referencedFiles[component])
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package deb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkListReferencedFiles(b *testing.B) {
|
||||||
|
const defaultComponent = "main"
|
||||||
|
const repoCount = 16
|
||||||
|
const repoPackagesCount = 1024
|
||||||
|
const uniqPackagesCount = 64
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp("", "aptly-bench")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
db, err := goleveldb.NewOpenDB(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
factory := NewCollectionFactory(db)
|
||||||
|
packageCollection := factory.PackageCollection()
|
||||||
|
repoCollection := factory.LocalRepoCollection()
|
||||||
|
publishCollection := factory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
sharedRefs := NewPackageRefList()
|
||||||
|
{
|
||||||
|
transaction, err := db.OpenTransaction()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for pkgIndex := 0; pkgIndex < repoPackagesCount-uniqPackagesCount; pkgIndex++ {
|
||||||
|
p := &Package{
|
||||||
|
Name: fmt.Sprintf("pkg-shared_%d", pkgIndex),
|
||||||
|
Version: "1",
|
||||||
|
Architecture: "amd64",
|
||||||
|
}
|
||||||
|
p.UpdateFiles(PackageFiles{PackageFile{
|
||||||
|
Filename: fmt.Sprintf("pkg-shared_%d.deb", pkgIndex),
|
||||||
|
}})
|
||||||
|
|
||||||
|
packageCollection.UpdateInTransaction(p, transaction)
|
||||||
|
sharedRefs.Refs = append(sharedRefs.Refs, p.Key(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sharedRefs)
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for repoIndex := 0; repoIndex < repoCount; repoIndex++ {
|
||||||
|
refs := NewPackageRefList()
|
||||||
|
|
||||||
|
transaction, err := db.OpenTransaction()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for pkgIndex := 0; pkgIndex < uniqPackagesCount; pkgIndex++ {
|
||||||
|
p := &Package{
|
||||||
|
Name: fmt.Sprintf("pkg%d_%d", repoIndex, pkgIndex),
|
||||||
|
Version: "1",
|
||||||
|
Architecture: "amd64",
|
||||||
|
}
|
||||||
|
p.UpdateFiles(PackageFiles{PackageFile{
|
||||||
|
Filename: fmt.Sprintf("pkg%d_%d.deb", repoIndex, pkgIndex),
|
||||||
|
}})
|
||||||
|
|
||||||
|
packageCollection.UpdateInTransaction(p, transaction)
|
||||||
|
refs.Refs = append(refs.Refs, p.Key(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(refs)
|
||||||
|
|
||||||
|
repo := NewLocalRepo(fmt.Sprintf("repo%d", repoIndex), "comment")
|
||||||
|
repo.DefaultDistribution = fmt.Sprintf("dist%d", repoIndex)
|
||||||
|
repo.DefaultComponent = defaultComponent
|
||||||
|
repo.UpdateRefList(refs.Merge(sharedRefs, false, true))
|
||||||
|
repoCollection.Add(repo)
|
||||||
|
|
||||||
|
publish, err := NewPublishedRepo("", "test", "", nil, []string{defaultComponent}, []interface{}{repo}, factory, false)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
publishCollection.Add(publish)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.CompactDB()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := publishCollection.listReferencedFilesByComponent("test", []string{defaultComponent}, factory, nil)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+144
-37
@@ -7,6 +7,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
@@ -133,19 +134,19 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
|||||||
s.packageCollection.Update(s.p2)
|
s.packageCollection.Update(s.p2)
|
||||||
s.packageCollection.Update(s.p3)
|
s.packageCollection.Update(s.p3)
|
||||||
|
|
||||||
s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
s.repo.SkipContents = true
|
s.repo.SkipContents = true
|
||||||
|
|
||||||
s.repo2, _ = NewPublishedRepo("", "ppa", "maverick", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
s.repo2, _ = NewPublishedRepo("", "ppa", "maverick", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
s.repo2.SkipContents = true
|
s.repo2.SkipContents = true
|
||||||
|
|
||||||
s.repo3, _ = NewPublishedRepo("", "linux", "natty", nil, []string{"main", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
s.repo3, _ = NewPublishedRepo("", "linux", "natty", nil, []string{"main", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
s.repo3.SkipContents = true
|
s.repo3.SkipContents = true
|
||||||
|
|
||||||
s.repo4, _ = NewPublishedRepo("", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
s.repo4, _ = NewPublishedRepo("", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
s.repo4.SkipContents = true
|
s.repo4.SkipContents = true
|
||||||
|
|
||||||
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
s.repo5.SkipContents = true
|
s.repo5.SkipContents = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,19 +178,71 @@ func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) {
|
|||||||
c.Check(s.repo3.RefList("main").Len(), Equals, 3)
|
c.Check(s.repo3.RefList("main").Len(), Equals, 3)
|
||||||
c.Check(s.repo3.RefList("contrib").Len(), Equals, 3)
|
c.Check(s.repo3.RefList("contrib").Len(), Equals, 3)
|
||||||
|
|
||||||
c.Check(func() { NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory) }, PanicMatches, "publish with empty sources")
|
c.Check(func() { NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory, false) }, PanicMatches, "publish with empty sources")
|
||||||
c.Check(func() {
|
c.Check(func() {
|
||||||
NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
}, PanicMatches, "sources and components should be equal in size")
|
}, PanicMatches, "sources and components should be equal in size")
|
||||||
c.Check(func() {
|
c.Check(func() {
|
||||||
NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory)
|
NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory, false)
|
||||||
}, PanicMatches, "interface conversion:.*")
|
}, PanicMatches, "interface conversion:.*")
|
||||||
|
|
||||||
_, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
_, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
c.Check(err, ErrorMatches, "duplicate component name: main")
|
c.Check(err, ErrorMatches, "duplicate component name: main")
|
||||||
|
|
||||||
_, err = NewPublishedRepo("", ".", "wheezy/updates", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
_, err = NewPublishedRepo("", ".", "wheezy/updates", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
c.Check(err, ErrorMatches, "invalid distribution wheezy/updates, '/' is not allowed")
|
c.Check(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedRepoSuite) TestMultiDistPool(c *C) {
|
||||||
|
repo, err := NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, true)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
err = repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
publishedStorage := files.NewPublishedStorage(s.root, "", "")
|
||||||
|
|
||||||
|
c.Check(repo.Architectures, DeepEquals, []string{"i386"})
|
||||||
|
|
||||||
|
rf, err := os.Open(filepath.Join(publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
cfr := NewControlFileReader(rf, true, false)
|
||||||
|
st, err := cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Check(st["Origin"], Equals, "ppa squeeze")
|
||||||
|
c.Check(st["Components"], Equals, "main")
|
||||||
|
c.Check(st["Architectures"], Equals, "i386")
|
||||||
|
|
||||||
|
pf, err := os.Open(filepath.Join(publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Packages"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
cfr = NewControlFileReader(pf, false, false)
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
st, err = cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Check(st["Filename"], Equals, "pool/squeeze/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb")
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err = cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(st, IsNil)
|
||||||
|
|
||||||
|
drf, err := os.Open(filepath.Join(publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
cfr = NewControlFileReader(drf, true, false)
|
||||||
|
st, err = cfr.ReadStanza()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Check(st["Archive"], Equals, "squeeze")
|
||||||
|
c.Check(st["Architecture"], Equals, "i386")
|
||||||
|
|
||||||
|
_, err = os.Stat(filepath.Join(publishedStorage.PublicPath(), "ppa/pool/squeeze/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb"))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) {
|
func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) {
|
||||||
@@ -248,7 +301,7 @@ func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) {
|
|||||||
errorExpected: "invalid prefix .*",
|
errorExpected: "invalid prefix .*",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
repo, err := NewPublishedRepo("", t.prefix, "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
repo, err := NewPublishedRepo("", t.prefix, "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
if t.errorExpected != "" {
|
if t.errorExpected != "" {
|
||||||
c.Check(err, ErrorMatches, t.errorExpected)
|
c.Check(err, ErrorMatches, t.errorExpected)
|
||||||
} else {
|
} else {
|
||||||
@@ -258,51 +311,51 @@ func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) {
|
func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) {
|
||||||
repo, err := NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.snapshot}, s.factory)
|
repo, err := NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "squeeze")
|
c.Check(repo.Distribution, Equals, "squeeze")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "wheezy", nil, []string{""}, []interface{}{s.snapshot}, s.factory)
|
repo, err = NewPublishedRepo("", "ppa", "wheezy", nil, []string{""}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "wheezy")
|
c.Check(repo.Distribution, Equals, "wheezy")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"non-free"}, []interface{}{s.snapshot}, s.factory)
|
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"non-free"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "squeeze")
|
c.Check(repo.Distribution, Equals, "squeeze")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"non-free"})
|
c.Check(repo.Components(), DeepEquals, []string{"non-free"})
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "squeeze", nil, []string{""}, []interface{}{s.localRepo}, s.factory)
|
repo, err = NewPublishedRepo("", "ppa", "squeeze", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "squeeze")
|
c.Check(repo.Distribution, Equals, "squeeze")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
||||||
|
|
||||||
_, err = NewPublishedRepo("", "ppa", "", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
_, err = NewPublishedRepo("", "ppa", "", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
c.Check(err, ErrorMatches, "unable to guess distribution name, please specify explicitly")
|
c.Check(err, ErrorMatches, "unable to guess distribution name, please specify explicitly")
|
||||||
|
|
||||||
s.localRepo.DefaultDistribution = "precise"
|
s.localRepo.DefaultDistribution = "precise"
|
||||||
s.localRepo.DefaultComponent = "contrib"
|
s.localRepo.DefaultComponent = "contrib"
|
||||||
s.factory.LocalRepoCollection().Update(s.localRepo)
|
s.factory.LocalRepoCollection().Update(s.localRepo)
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory)
|
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "precise")
|
c.Check(repo.Distribution, Equals, "precise")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
||||||
|
|
||||||
s.localRepo.DefaultDistribution = "precise/updates"
|
s.localRepo.DefaultDistribution = "precise/updates"
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory)
|
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "precise-updates")
|
c.Check(repo.Distribution, Equals, "precise/updates")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
||||||
|
|
||||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(repo.Distribution, Equals, "squeeze")
|
c.Check(repo.Distribution, Equals, "squeeze")
|
||||||
c.Check(repo.Components(), DeepEquals, []string{"contrib", "main"})
|
c.Check(repo.Components(), DeepEquals, []string{"contrib", "main"})
|
||||||
|
|
||||||
_, err = NewPublishedRepo("", "ppa", "", nil, []string{"", ""}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
_, err = NewPublishedRepo("", "ppa", "", nil, []string{"", ""}, []interface{}{s.snapshot, s.snapshot2}, s.factory, false)
|
||||||
c.Check(err, ErrorMatches, "duplicate component name: main")
|
c.Check(err, ErrorMatches, "duplicate component name: main")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,10 +443,10 @@ func (s *PublishedRepoSuite) TestString(c *C) {
|
|||||||
"ppa/squeeze [] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
"ppa/squeeze [] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||||
c.Check(s.repo2.String(), Equals,
|
c.Check(s.repo2.String(), Equals,
|
||||||
"ppa/maverick [] publishes {main: [local1]: comment1}")
|
"ppa/maverick [] publishes {main: [local1]: comment1}")
|
||||||
repo, _ := NewPublishedRepo("", "", "squeeze", []string{"s390"}, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
repo, _ := NewPublishedRepo("", "", "squeeze", []string{"s390"}, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
c.Check(repo.String(), Equals,
|
c.Check(repo.String(), Equals,
|
||||||
"./squeeze [s390] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
"./squeeze [s390] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||||
repo, _ = NewPublishedRepo("", "", "squeeze", []string{"i386", "amd64"}, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
repo, _ = NewPublishedRepo("", "", "squeeze", []string{"i386", "amd64"}, []string{"main"}, []interface{}{s.snapshot}, s.factory, false)
|
||||||
c.Check(repo.String(), Equals,
|
c.Check(repo.String(), Equals,
|
||||||
"./squeeze [i386, amd64] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
"./squeeze [i386, amd64] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||||
repo.Origin = "myorigin"
|
repo.Origin = "myorigin"
|
||||||
@@ -450,13 +503,22 @@ type PublishedRepoCollectionSuite struct {
|
|||||||
var _ = Suite(&PublishedRepoCollectionSuite{})
|
var _ = Suite(&PublishedRepoCollectionSuite{})
|
||||||
|
|
||||||
func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
|
func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
|
||||||
|
s.SetUpPackages()
|
||||||
|
|
||||||
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
||||||
s.factory = NewCollectionFactory(s.db)
|
s.factory = NewCollectionFactory(s.db)
|
||||||
|
|
||||||
s.snapshotCollection = s.factory.SnapshotCollection()
|
s.snapshotCollection = s.factory.SnapshotCollection()
|
||||||
|
|
||||||
s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1")
|
snap1Refs := NewPackageRefList()
|
||||||
s.snap2 = NewSnapshotFromPackageList("snap2", []*Snapshot{}, NewPackageList(), "desc2")
|
snap1Refs.Refs = [][]byte{s.p1.Key(""), s.p2.Key("")}
|
||||||
|
sort.Sort(snap1Refs)
|
||||||
|
s.snap1 = NewSnapshotFromRefList("snap1", []*Snapshot{}, snap1Refs, "desc1")
|
||||||
|
|
||||||
|
snap2Refs := NewPackageRefList()
|
||||||
|
snap2Refs.Refs = [][]byte{s.p3.Key("")}
|
||||||
|
sort.Sort(snap2Refs)
|
||||||
|
s.snap2 = NewSnapshotFromRefList("snap2", []*Snapshot{}, snap2Refs, "desc2")
|
||||||
|
|
||||||
s.snapshotCollection.Add(s.snap1)
|
s.snapshotCollection.Add(s.snap1)
|
||||||
s.snapshotCollection.Add(s.snap2)
|
s.snapshotCollection.Add(s.snap2)
|
||||||
@@ -464,11 +526,11 @@ func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
|
|||||||
s.localRepo = NewLocalRepo("local1", "comment1")
|
s.localRepo = NewLocalRepo("local1", "comment1")
|
||||||
s.factory.LocalRepoCollection().Add(s.localRepo)
|
s.factory.LocalRepoCollection().Add(s.localRepo)
|
||||||
|
|
||||||
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main", "contrib"}, []interface{}{s.snap2, s.snap1}, s.factory)
|
s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main", "contrib"}, []interface{}{s.snap2, s.snap1}, s.factory, false)
|
||||||
s.repo3, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap2}, s.factory)
|
s.repo3, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap2}, s.factory, false)
|
||||||
s.repo4, _ = NewPublishedRepo("", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
s.repo4, _ = NewPublishedRepo("", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory, false)
|
||||||
|
|
||||||
s.collection = s.factory.PublishedRepoCollection()
|
s.collection = s.factory.PublishedRepoCollection()
|
||||||
}
|
}
|
||||||
@@ -534,7 +596,7 @@ func (s *PublishedRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
|||||||
c.Assert(r.sourceItems["main"].snapshot, IsNil)
|
c.Assert(r.sourceItems["main"].snapshot, IsNil)
|
||||||
c.Assert(s.collection.LoadComplete(r, s.factory), IsNil)
|
c.Assert(s.collection.LoadComplete(r, s.factory), IsNil)
|
||||||
c.Assert(r.Sources["main"], Equals, s.repo1.sourceItems["main"].snapshot.UUID)
|
c.Assert(r.Sources["main"], Equals, s.repo1.sourceItems["main"].snapshot.UUID)
|
||||||
c.Assert(r.RefList("main").Len(), Equals, 0)
|
c.Assert(r.RefList("main").Len(), Equals, 2)
|
||||||
|
|
||||||
r, err = collection.ByStoragePrefixDistribution("", "ppa", "precise")
|
r, err = collection.ByStoragePrefixDistribution("", "ppa", "precise")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@@ -625,6 +687,51 @@ func (s *PublishedRepoCollectionSuite) TestByLocalRepo(c *C) {
|
|||||||
c.Check(s.collection.ByLocalRepo(s.localRepo), DeepEquals, []*PublishedRepo{s.repo4, s.repo5})
|
c.Check(s.collection.ByLocalRepo(s.localRepo), DeepEquals, []*PublishedRepo{s.repo4, s.repo5})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
|
||||||
|
c.Check(s.factory.PackageCollection().Update(s.p1), IsNil)
|
||||||
|
c.Check(s.factory.PackageCollection().Update(s.p2), IsNil)
|
||||||
|
c.Check(s.factory.PackageCollection().Update(s.p3), IsNil)
|
||||||
|
|
||||||
|
c.Check(s.collection.Add(s.repo1), IsNil)
|
||||||
|
c.Check(s.collection.Add(s.repo2), IsNil)
|
||||||
|
c.Check(s.collection.Add(s.repo4), IsNil)
|
||||||
|
c.Check(s.collection.Add(s.repo5), IsNil)
|
||||||
|
|
||||||
|
files, err := s.collection.listReferencedFilesByComponent(".", []string{"main", "contrib"}, s.factory, nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
for _, v := range files {
|
||||||
|
sort.Strings(v)
|
||||||
|
}
|
||||||
|
c.Check(files, DeepEquals, map[string][]string{
|
||||||
|
"contrib": {
|
||||||
|
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
||||||
|
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
|
||||||
|
},
|
||||||
|
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
|
||||||
|
})
|
||||||
|
|
||||||
|
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
|
||||||
|
s.snapshotCollection.Add(snap3)
|
||||||
|
|
||||||
|
// Ensure that adding a second publish point with matching files doesn't give duplicate results.
|
||||||
|
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(s.collection.Add(repo3), IsNil)
|
||||||
|
|
||||||
|
files, err = s.collection.listReferencedFilesByComponent(".", []string{"main", "contrib"}, s.factory, nil)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
for _, v := range files {
|
||||||
|
sort.Strings(v)
|
||||||
|
}
|
||||||
|
c.Check(files, DeepEquals, map[string][]string{
|
||||||
|
"contrib": {
|
||||||
|
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
||||||
|
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
|
||||||
|
},
|
||||||
|
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type PublishedRepoRemoveSuite struct {
|
type PublishedRepoRemoveSuite struct {
|
||||||
PackageListMixinSuite
|
PackageListMixinSuite
|
||||||
db database.Storage
|
db database.Storage
|
||||||
@@ -650,11 +757,11 @@ func (s *PublishedRepoRemoveSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.snapshotCollection.Add(s.snap1)
|
s.snapshotCollection.Add(s.snap1)
|
||||||
|
|
||||||
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
s.repo3, _ = NewPublishedRepo("", "ppa", "meduza", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
s.repo3, _ = NewPublishedRepo("", "ppa", "meduza", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
s.repo4, _ = NewPublishedRepo("", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory)
|
s.repo4, _ = NewPublishedRepo("", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory)
|
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory, false)
|
||||||
|
|
||||||
s.collection = s.factory.PublishedRepoCollection()
|
s.collection = s.factory.PublishedRepoCollection()
|
||||||
s.collection.Add(s.repo1)
|
s.collection.Add(s.repo1)
|
||||||
|
|||||||
+5
-5
@@ -138,7 +138,7 @@ func (q *NotQuery) Matches(pkg PackageLike) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fast is false
|
// Fast is false
|
||||||
func (q *NotQuery) Fast(list PackageCatalog) bool {
|
func (q *NotQuery) Fast(_ PackageCatalog) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ func (q *FieldQuery) Query(list PackageCatalog) (result *PackageList) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fast depends on the query
|
// Fast depends on the query
|
||||||
func (q *FieldQuery) Fast(list PackageCatalog) bool {
|
func (q *FieldQuery) Fast(_ PackageCatalog) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ func (q *PkgQuery) Matches(pkg PackageLike) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fast is always true for package query
|
// Fast is always true for package query
|
||||||
func (q *PkgQuery) Fast(list PackageCatalog) bool {
|
func (q *PkgQuery) Fast(_ PackageCatalog) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,12 +280,12 @@ func (q *PkgQuery) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Matches on specific properties
|
// Matches on specific properties
|
||||||
func (q *MatchAllQuery) Matches(pkg PackageLike) bool {
|
func (q *MatchAllQuery) Matches(_ PackageLike) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast is always true for match all query
|
// Fast is always true for match all query
|
||||||
func (q *MatchAllQuery) Fast(list PackageCatalog) bool {
|
func (q *MatchAllQuery) Fast(_ PackageCatalog) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-48
@@ -71,7 +71,9 @@ func (l *PackageRefList) Encode() []byte {
|
|||||||
|
|
||||||
// Decode decodes msgpack representation into PackageRefLit
|
// Decode decodes msgpack representation into PackageRefLit
|
||||||
func (l *PackageRefList) Decode(input []byte) error {
|
func (l *PackageRefList) Decode(input []byte) error {
|
||||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
handle := &codec.MsgpackHandle{}
|
||||||
|
handle.ZeroCopy = true
|
||||||
|
decoder := codec.NewDecoderBytes(input, handle)
|
||||||
return decoder.Decode(l)
|
return decoder.Decode(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,31 +196,21 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle
|
|||||||
|
|
||||||
// until we reached end of both lists
|
// until we reached end of both lists
|
||||||
for il < ll || ir < lr {
|
for il < ll || ir < lr {
|
||||||
// if we've exhausted left list, pull the rest from the right
|
var rl, rr []byte
|
||||||
if il == ll {
|
if il < ll {
|
||||||
pr, err = packageCollection.ByKey(r.Refs[ir])
|
rl = l.Refs[il]
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, PackageDiff{Left: nil, Right: pr})
|
|
||||||
ir++
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
// if we've exhausted right list, pull the rest from the left
|
if ir < lr {
|
||||||
if ir == lr {
|
rr = r.Refs[ir]
|
||||||
pl, err = packageCollection.ByKey(l.Refs[il])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, PackageDiff{Left: pl, Right: nil})
|
|
||||||
il++
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// refs on both sides are present, load them
|
|
||||||
rl, rr := l.Refs[il], r.Refs[ir]
|
|
||||||
// compare refs
|
// compare refs
|
||||||
rel := bytes.Compare(rl, rr)
|
rel := bytes.Compare(rl, rr)
|
||||||
|
// an unset ref is less than all others, but since it represents the end
|
||||||
|
// of a reflist, it should be *greater*, so flip the comparison result
|
||||||
|
if rl == nil || rr == nil {
|
||||||
|
rel = -rel
|
||||||
|
}
|
||||||
|
|
||||||
if rel == 0 {
|
if rel == 0 {
|
||||||
// refs are identical, so are packages, advance pointer
|
// refs are identical, so are packages, advance pointer
|
||||||
@@ -227,14 +219,14 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle
|
|||||||
pl, pr = nil, nil
|
pl, pr = nil, nil
|
||||||
} else {
|
} else {
|
||||||
// load pl & pr if they haven't been loaded before
|
// load pl & pr if they haven't been loaded before
|
||||||
if pl == nil {
|
if pl == nil && rl != nil {
|
||||||
pl, err = packageCollection.ByKey(rl)
|
pl, err = packageCollection.ByKey(rl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pr == nil {
|
if pr == nil && rr != nil {
|
||||||
pr, err = packageCollection.ByKey(rr)
|
pr, err = packageCollection.ByKey(rr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -310,38 +302,41 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching, ignoreConfli
|
|||||||
overridenName = nil
|
overridenName = nil
|
||||||
overriddenArch = nil
|
overriddenArch = nil
|
||||||
} else {
|
} else {
|
||||||
partsL := bytes.Split(rl, []byte(" "))
|
if !ignoreConflicting || overrideMatching {
|
||||||
archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2]
|
partsL := bytes.Split(rl, []byte(" "))
|
||||||
|
archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2]
|
||||||
|
|
||||||
partsR := bytes.Split(rr, []byte(" "))
|
partsR := bytes.Split(rr, []byte(" "))
|
||||||
archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2]
|
archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2]
|
||||||
|
|
||||||
if !ignoreConflicting && bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) {
|
if !ignoreConflicting && bytes.Equal(archL, archR) &&
|
||||||
// conflicting duplicates with same arch, name, version, but different file hash
|
bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) {
|
||||||
result.Refs = append(result.Refs, r.Refs[ir])
|
// conflicting duplicates with same arch, name, version, but different file hash
|
||||||
il++
|
|
||||||
ir++
|
|
||||||
overridenName = nil
|
|
||||||
overriddenArch = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if overrideMatching {
|
|
||||||
if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) {
|
|
||||||
// this package has already been overridden on the right
|
|
||||||
il++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) {
|
|
||||||
// override with package from the right
|
|
||||||
result.Refs = append(result.Refs, r.Refs[ir])
|
result.Refs = append(result.Refs, r.Refs[ir])
|
||||||
il++
|
il++
|
||||||
ir++
|
ir++
|
||||||
overriddenArch = archL
|
overridenName = nil
|
||||||
overridenName = nameL
|
overriddenArch = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if overrideMatching {
|
||||||
|
if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) {
|
||||||
|
// this package has already been overridden on the right
|
||||||
|
il++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) {
|
||||||
|
// override with package from the right
|
||||||
|
result.Refs = append(result.Refs, r.Refs[ir])
|
||||||
|
il++
|
||||||
|
ir++
|
||||||
|
overriddenArch = archL
|
||||||
|
overridenName = nameL
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise append smallest of two
|
// otherwise append smallest of two
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package deb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkReflistSimpleMerge(b *testing.B) {
|
||||||
|
const count = 4096
|
||||||
|
|
||||||
|
l := NewPackageRefList()
|
||||||
|
r := NewPackageRefList()
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
l.Refs = append(l.Refs, []byte(fmt.Sprintf("Pamd64 pkg%d %d", i, i)))
|
||||||
|
} else {
|
||||||
|
r.Refs = append(r.Refs, []byte(fmt.Sprintf("Pamd64 pkg%d %d", i, i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(l)
|
||||||
|
sort.Sort(r)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
l.Merge(r, false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkReflistDecode(b *testing.B) {
|
||||||
|
const count = 4096
|
||||||
|
|
||||||
|
r := NewPackageRefList()
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
r.Refs = append(r.Refs, []byte(fmt.Sprintf("Pamd64 pkg%d %d", i, i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(r)
|
||||||
|
data := r.Encode()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
(&PackageRefList{}).Decode(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -237,6 +237,41 @@ func (s *PackageRefListSuite) TestDiff(c *C) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PackageRefListSuite) TestDiffCompactsAtEnd(c *C) {
|
||||||
|
db, _ := goleveldb.NewOpenDB(c.MkDir())
|
||||||
|
coll := NewPackageCollection(db)
|
||||||
|
|
||||||
|
packages := []*Package{
|
||||||
|
{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //0
|
||||||
|
{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //1
|
||||||
|
{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //2
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range packages {
|
||||||
|
coll.Update(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
listA := NewPackageList()
|
||||||
|
listA.Add(packages[0])
|
||||||
|
|
||||||
|
listB := NewPackageList()
|
||||||
|
listB.Add(packages[1])
|
||||||
|
listB.Add(packages[2])
|
||||||
|
|
||||||
|
reflistA := NewPackageRefListFromPackageList(listA)
|
||||||
|
reflistB := NewPackageRefListFromPackageList(listB)
|
||||||
|
|
||||||
|
diffAB, err := reflistA.Diff(reflistB, coll)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(diffAB, HasLen, 2)
|
||||||
|
|
||||||
|
c.Check(diffAB[0].Left, IsNil)
|
||||||
|
c.Check(diffAB[0].Right.String(), Equals, "app_1.1~bp2_amd64")
|
||||||
|
|
||||||
|
c.Check(diffAB[1].Left.String(), Equals, "app_1.1~bp1_i386")
|
||||||
|
c.Check(diffAB[1].Right.String(), Equals, "app_1.1~bp2_i386")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PackageRefListSuite) TestMerge(c *C) {
|
func (s *PackageRefListSuite) TestMerge(c *C) {
|
||||||
db, _ := goleveldb.NewOpenDB(c.MkDir())
|
db, _ := goleveldb.NewOpenDB(c.MkDir())
|
||||||
coll := NewPackageCollection(db)
|
coll := NewPackageCollection(db)
|
||||||
|
|||||||
+24
-10
@@ -259,6 +259,9 @@ func (repo *RemoteRepo) UdebPath(component string, architecture string) string {
|
|||||||
// InstallerPath returns path of Packages files for given component and
|
// InstallerPath returns path of Packages files for given component and
|
||||||
// architecture
|
// architecture
|
||||||
func (repo *RemoteRepo) InstallerPath(component string, architecture string) string {
|
func (repo *RemoteRepo) InstallerPath(component string, architecture string) string {
|
||||||
|
if repo.Distribution == aptly.DistributionFocal {
|
||||||
|
return fmt.Sprintf("%s/installer-%s/current/legacy-images/SHA256SUMS", component, architecture)
|
||||||
|
}
|
||||||
return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture)
|
return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,17 +273,29 @@ func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch updates information about repository
|
// Fetch updates information about repository
|
||||||
func (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier pgp.Verifier) error {
|
func (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier pgp.Verifier, ignoreSignatures bool) error {
|
||||||
var (
|
var (
|
||||||
release, inrelease, releasesig *os.File
|
release, inrelease, releasesig *os.File
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if verifier == nil {
|
if ignoreSignatures {
|
||||||
// 0. Just download release file to temporary URL
|
// 0. Just download release file to temporary URL
|
||||||
release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String())
|
release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// 0.1 try downloading InRelease, ignore and strip signature
|
||||||
|
inrelease, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("InRelease").String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if verifier == nil {
|
||||||
|
return fmt.Errorf("no verifier specified")
|
||||||
|
}
|
||||||
|
release, err = verifier.ExtractClearsigned(inrelease)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
goto ok
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 1. try InRelease file
|
// 1. try InRelease file
|
||||||
@@ -428,8 +443,7 @@ ok:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DownloadPackageIndexes downloads & parses package index files
|
// DownloadPackageIndexes downloads & parses package index files
|
||||||
func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, verifier pgp.Verifier, collectionFactory *CollectionFactory,
|
func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, verifier pgp.Verifier, _ *CollectionFactory, ignoreSignatures bool, ignoreChecksums bool) error {
|
||||||
ignoreMismatch bool) error {
|
|
||||||
if repo.packageList != nil {
|
if repo.packageList != nil {
|
||||||
panic("packageList != nil")
|
panic("packageList != nil")
|
||||||
}
|
}
|
||||||
@@ -462,14 +476,14 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
|
|||||||
|
|
||||||
for _, info := range packagesPaths {
|
for _, info := range packagesPaths {
|
||||||
path, kind, component, architecture := info[0], info[1], info[2], info[3]
|
path, kind, component, architecture := info[0], info[1], info[2], info[3]
|
||||||
packagesReader, packagesFile, err := http.DownloadTryCompression(gocontext.TODO(), d, repo.IndexesRootURL(), path, repo.ReleaseFiles, ignoreMismatch)
|
packagesReader, packagesFile, err := http.DownloadTryCompression(gocontext.TODO(), d, repo.IndexesRootURL(), path, repo.ReleaseFiles, ignoreChecksums)
|
||||||
|
|
||||||
isInstaller := kind == PackageTypeInstaller
|
isInstaller := kind == PackageTypeInstaller
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*http.NoCandidateFoundError); isInstaller && ok {
|
if _, ok := err.(*http.NoCandidateFoundError); isInstaller && ok {
|
||||||
// checking if gpg file is only needed when checksums matches are required.
|
// checking if gpg file is only needed when checksums matches are required.
|
||||||
// otherwise there actually has been no candidate found and we can continue
|
// otherwise there actually has been no candidate found and we can continue
|
||||||
if ignoreMismatch {
|
if ignoreChecksums {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +500,7 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if verifier != nil {
|
if verifier != nil && !ignoreSignatures {
|
||||||
hashsumGpgPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path + ".gpg"}).String()
|
hashsumGpgPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path + ".gpg"}).String()
|
||||||
var filesig *os.File
|
var filesig *os.File
|
||||||
filesig, err = http.DownloadTemp(gocontext.TODO(), d, hashsumGpgPath)
|
filesig, err = http.DownloadTemp(gocontext.TODO(), d, hashsumGpgPath)
|
||||||
@@ -777,7 +791,7 @@ func (collection *RemoteRepoCollection) search(filter func(*RemoteRepo) bool, un
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error {
|
collection.db.ProcessByPrefix([]byte("R"), func(_, blob []byte) error {
|
||||||
r := &RemoteRepo{}
|
r := &RemoteRepo{}
|
||||||
if err := r.Decode(blob); err != nil {
|
if err := r.Decode(blob); err != nil {
|
||||||
log.Printf("Error decoding remote repo: %s\n", err)
|
log.Printf("Error decoding remote repo: %s\n", err)
|
||||||
@@ -880,7 +894,7 @@ func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error)
|
|||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error) error {
|
func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error) error {
|
||||||
return collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error {
|
return collection.db.ProcessByPrefix([]byte("R"), func(_, blob []byte) error {
|
||||||
r := &RemoteRepo{}
|
r := &RemoteRepo{}
|
||||||
if err := r.Decode(blob); err != nil {
|
if err := r.Decode(blob); err != nil {
|
||||||
log.Printf("Error decoding mirror: %s\n", err)
|
log.Printf("Error decoding mirror: %s\n", err)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user