Merge remote-tracking branch 'official/release/1.6.0' into upstream/1.6

This commit is contained in:
Sébastien Delafond
2024-12-11 17:42:22 +01:00
78 changed files with 3502 additions and 1355 deletions
+4 -1
View File
@@ -70,4 +70,7 @@ usr/bin/aptly
dpkgs/
debian/changelog.dpkg-bak
docs/
docs/docs.go
docs/swagger.json
docs/swagger.yaml
docs/swagger.conf
+2
View File
@@ -66,3 +66,5 @@ List of contributors, in chronological order:
* Andrey Loukhnov (https://github.com/aol-nnov)
* Christoph Fiehe (https://github.com/cfiehe)
* Blake Kostner (https://github.com/btkostner)
* Leigh London (https://github.com/leighlondon)
* Gordian Schönherr (https://github.com/schoenherrg)
+15 -51
View File
@@ -110,15 +110,13 @@ make docker-build
To run aptly commands in the development docker container, run:
```
make docker-aptly
make docker-shell
```
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
$ make docker-shell
aptly@b43e8473ef81:/work/src$ aptly version
aptly version: 1.5.0+189+g0fc90dff
```
@@ -152,42 +150,28 @@ Run `make help` for more information.
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:
Building aptly requires go version 1.22.
make prepare dev system/env
On Debian bookworm with backports enabled, go can be installed with:
This is usually one-time action.
Aptly is using Go modules to manage dependencies, download modules using:
make modules
apt install -t bookworm-backports golang-go
#### Building
If you want to build aptly binary from your current source tree, run:
To build aptly, run:
make build
Run aptly:
build/aptly
To install aptly into `$GOPATH/bin`, run:
make install
This would build `aptly` in `$GOPATH/bin`, so depending on your `$PATH`, you should be able to run it immediately with:
aptly
Or, if it's not on your path:
~/go/bin/aptly
#### Unit-tests
aptly has two kinds of tests: unit-tests and functional (system) tests. Functional tests are preferred way to test any
@@ -244,26 +228,6 @@ There are some packages available under `system/files/` directory which are used
this default location. You can run aptly under different user or by using non-default config location with non-default
aptly root directory.
#### Style Checks
Style checks could be run with:
make check
aptly is using [golangci-lint](https://github.com/golangci/golangci-lint) to run style checks on Go code. Configuration
for the linter could be found in [.golangci.yml](.golangci.yml) file.
Python code (system tests) are linted with [flake8 tool](https://pypi.python.org/pypi/flake8).
#### Vendored Code
aptly is using Go vendoring for all the libraries aptly depends upon. `vendor/` directory is checked into the source
repository to avoid any problems if source repositories go away. Go build process will automatically prefer vendored
packages over packages in `$GOPATH`.
If you want to update vendored dependencies or to introduce new dependency, use [dep tool](https://github.com/golang/dep).
Usually all you need is `dep ensure` or `dep ensure -update`.
### man Page
aptly is using combination of [Go templates](http://godoc.org/text/template) and automatically generated text to build `aptly.1` man page. If either source
+19 -13
View File
@@ -7,7 +7,7 @@ COVERAGE_DIR?=$(shell mktemp -d)
GOOS=$(shell go env GOHOSTOS)
GOARCH=$(shell go env GOHOSTARCH)
# Uncomment to update test outputs
# Uncomment to update system test gold files
# CAPTURE := "--capture"
help: ## Print this help
@@ -44,27 +44,30 @@ version: ## Print aptly version
swagger-install:
# Install swag
@test -f $(BINPATH)/swag || GOOS=linux GOARCH=amd64 go install github.com/swaggo/swag/cmd/swag@latest
@test -f $(BINPATH)/swag || GOOS= GOARCH= go install github.com/swaggo/swag/cmd/swag@latest
# Generate swagger.conf
cp docs/swagger.conf.tpl docs/swagger.conf
echo "// @version $(VERSION)" >> docs/swagger.conf
azurite-start:
azurite & \
azurite -l /tmp/aptly-azurite & \
echo $$! > ~/.azurite.pid
azurite-stop:
kill `cat ~/.azurite.pid`
@kill `cat ~/.azurite.pid`
swagger: swagger-install
# Generate swagger docs
@PATH=$(BINPATH)/:$(PATH) swag init --markdownFiles docs
@PATH=$(BINPATH)/:$(PATH) swag init --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
etcd-install:
# Install etcd
test -d /srv/etcd || system/t13_etcd/install-etcd.sh
test -d /tmp/aptly-etcd || system/t13_etcd/install-etcd.sh
flake8: ## run flake8 on system test python files
flake8 system/
lint:
lint: prepare
# Install golangci-lint
@test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
# Running lint
@@ -76,17 +79,19 @@ build: prepare swagger ## Build aptly
install:
@echo "\e[33m\e[1mBuilding aptly ...\e[0m"
go generate
# go generate
@go generate
# go install -v
@out=`mktemp`; if ! go install -v > $$out 2>&1; then cat $$out; rm -f $$out; echo "\nBuild failed\n"; exit 1; else rm -f $$out; fi
test: prepare swagger etcd-install ## Run unit tests
@echo "\e[33m\e[1mStarting etcd ...\e[0m"
@mkdir -p /tmp/etcd-data; system/t13_etcd/start-etcd.sh > /tmp/etcd-data/etcd.log 2>&1 &
@mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 &
@echo "\e[33m\e[1mRunning go test ...\e[0m"
go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
@echo "\e[33m\e[1mStopping etcd ...\e[0m"
@pid=`cat /tmp/etcd.pid`; kill $$pid
@rm -f /tmp/etcd-data/etcd.log
@rm -f /tmp/aptly-etcd-data/etcd.log
@ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret
system-test: prepare swagger etcd-install ## Run system tests
@@ -106,8 +111,8 @@ bench:
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,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142
sed -i /enable_swagger_endpoint/s/false/true/ ~/.aptly.conf
PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs --generalInfo docs/swagger.conf' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142
dpkg: prepare swagger ## Build debian packages
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
@@ -205,7 +210,8 @@ man: ## Create man pages
clean: ## remove local build and module cache
# Clean all generated and build files
test -d .go/ && chmod u+w -R .go/ && rm -rf .go/ || true
test ! -e .go || find .go/ -type d ! -perm -u=w -exec chmod u+w {} \;
rm -rf .go/
rm -rf build/ obj-*-linux-gnu* tmp/
rm -f unit.out aptly.test VERSION docs/docs.go docs/swagger.json docs/swagger.yaml docs/swagger.conf
find system/ -type d -name __pycache__ -exec rm -rf {} \; 2>/dev/null || true
+52 -7
View File
@@ -23,12 +23,43 @@ import (
// 3. SnapshotCollection
// 4. PublishedRepoCollection
// GET /api/version
type aptlyVersion struct {
// Aptly Version
Version string `json:"Version"`
}
// @Summary Aptly Version
// @Description **Get aptly version**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/version
// @Description {"Version":"0.9~dev"}
// @Description ```
// @Tags Status
// @Produce json
// @Success 200 {object} aptlyVersion
// @Router /api/version [get]
func apiVersion(c *gin.Context) {
c.JSON(200, gin.H{"Version": aptly.Version})
}
// GET /api/ready
type aptlyStatus struct {
// Aptly Status
Status string `json:"Status" example:"'Aptly is ready', 'Aptly is unavailable', 'Aptly is healthy'"`
}
// @Summary Get Ready State
// @Description **Get aptly ready state**
// @Description
// @Description Return aptly ready state:
// @Description - `Aptly is ready` (HTTP 200)
// @Description - `Aptly is unavailable` (HTTP 503)
// @Tags Status
// @Produce json
// @Success 200 {object} aptlyStatus "Aptly is ready"
// @Failure 503 {object} aptlyStatus "Aptly is unavailable"
// @Router /api/ready [get]
func apiReady(isReady *atomic.Value) func(*gin.Context) {
return func(c *gin.Context) {
if isReady == nil || !isReady.Load().(bool) {
@@ -40,7 +71,15 @@ func apiReady(isReady *atomic.Value) func(*gin.Context) {
}
}
// GET /api/healthy
// @Summary Get Health State
// @Description **Get aptly health state**
// @Description
// @Description Return aptly health state:
// @Description - `Aptly is healthy` (HTTP 200)
// @Tags Status
// @Produce json
// @Success 200 {object} aptlyStatus
// @Router /api/healthy [get]
func apiHealthy(c *gin.Context) {
c.JSON(200, gin.H{"Status": "Aptly is healthy"})
}
@@ -227,8 +266,13 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
list.PrepareIndex()
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
nil, context.DependencyOptions(), architecturesList)
list, err = list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{q},
WithDependencies: withDeps,
Source: nil,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
})
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err))
return
@@ -244,8 +288,9 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
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{})
tmpList, err := list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{versionQ},
})
if err == nil {
if tmpList.Len() > 0 {
+10 -2
View File
@@ -11,9 +11,17 @@ import (
"github.com/gin-gonic/gin"
)
// POST /api/db/cleanup
// @Summary DB Cleanup
// @Description **Cleanup Aptly DB**
// @Description Database cleanup removes information about unreferenced packages and deletes files in the package pool that arent used by packages anymore.
// @Description It is a good idea to run this command after massive deletion of mirrors, snapshots or local repos.
// @Tags Database
// @Produce json
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} string "Output"
// @Failure 404 {object} Error "Not Found"
// @Router /api/db/cleanup [post]
func apiDbCleanup(c *gin.Context) {
resources := []string{string(task.AllResourcesKey)}
maybeRunTaskInBackground(c, "Clean up db", resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
var err error
+75 -8
View File
@@ -34,10 +34,16 @@ func verifyDir(c *gin.Context) bool {
return true
}
// @Summary Get files
// @Description Get list of uploaded files.
// @Summary List Directories
// @Description **Get list of upload directories**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/files
// @Description ["aptly-0.9"]
// @Description ```
// @Tags Files
// @Produce json
// @Produce json
// @Success 200 {array} string "List of files"
// @Router /api/files [get]
func apiFilesListDirs(c *gin.Context) {
@@ -67,7 +73,27 @@ func apiFilesListDirs(c *gin.Context) {
c.JSON(200, list)
}
// POST /files/:dir/
// @Summary Upload Files
// @Description **Upload files to a directory**
// @Description
// @Description - one or more files can be uploaded
// @Description - existing uploaded are overwritten
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X POST -F file=@aptly_0.9~dev+217+ge5d646c_i386.deb http://localhost:8080/api/files/aptly-0.9
// @Description ["aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb"]
// @Description ```
// @Tags Files
// @Accept multipart/form-data
// @Param dir path string true "Directory to upload files to. Created if does not exist"
// @Param files formData file true "Files to upload"
// @Produce json
// @Success 200 {array} string "list of uploaded files"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir} [post]
func apiFilesUpload(c *gin.Context) {
if !verifyDir(c) {
return
@@ -118,10 +144,23 @@ func apiFilesUpload(c *gin.Context) {
apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc()
c.JSON(200, stored)
}
// GET /files/:dir
// @Summary List Files
// @Description **Show uploaded files in upload directory**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/files/aptly-0.9
// @Description ["aptly_0.9~dev+217+ge5d646c_i386.deb"]
// @Description ```
// @Tags Files
// @Produce json
// @Param dir path string true "Directory to list"
// @Success 200 {array} string "Files found in directory"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir} [get]
func apiFilesListFiles(c *gin.Context) {
if !verifyDir(c) {
return
@@ -159,7 +198,20 @@ func apiFilesListFiles(c *gin.Context) {
c.JSON(200, list)
}
// DELETE /files/:dir
// @Summary Delete Directory
// @Description **Delete upload directory and uploaded files within**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9
// @Description {}
// @Description ```
// @Tags Files
// @Produce json
// @Param dir path string true "Directory"
// @Success 200 {object} string "msg"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir} [delete]
func apiFilesDeleteDir(c *gin.Context) {
if !verifyDir(c) {
return
@@ -174,7 +226,22 @@ func apiFilesDeleteDir(c *gin.Context) {
c.JSON(200, gin.H{})
}
// DELETE /files/:dir/:name
// @Summary Delete File
// @Description **Delete a uploaded file in upload directory**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb
// @Description {}
// @Description ```
// @Tags Files
// @Produce json
// @Param dir path string true "Directory to delete from"
// @Param name path string true "File to delete"
// @Success 200 {object} string "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir}/{name} [delete]
func apiFilesDeleteFile(c *gin.Context) {
if !verifyDir(c) {
return
+22 -8
View File
@@ -12,15 +12,29 @@ import (
"github.com/gin-gonic/gin"
)
// POST /api/gpg
func apiGPGAddKey(c *gin.Context) {
var b struct {
Keyserver string
GpgKeyID string
GpgKeyArmor string
Keyring string
}
type gpgAddKeyParams struct {
// Keyserver, when downloading GpgKeyIDs
Keyserver string `json:"Keyserver" example:"hkp://keyserver.ubuntu.com:80"`
// GpgKeyIDs to download from Keyserver, comma separated list
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500,8B48AD6246925553"`
// Armored gpg public ket, instead of downloading from keyserver
GpgKeyArmor string `json:"GpgKeyArmor" example:""`
// Keyring for adding the keys (default: trustedkeys.gpg)
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
}
// @Summary Add GPG Keys
// @Description **Adds GPG keys to aptly keyring**
// @Description
// @Description Add GPG public keys for veryfing remote repositories for mirroring.
// @Tags Mirrors
// @Produce json
// @Success 200 {object} string "OK"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Router /api/gpg [post]
func apiGPGAddKey(c *gin.Context) {
b := gpgAddKeyParams{}
if c.Bind(&b) != nil {
return
}
+21 -1
View File
@@ -12,7 +12,27 @@ import (
"github.com/gin-gonic/gin"
)
// GET /api/graph.:ext?layout=[vertical|horizontal(default)]
// @Summary Graph Output
// @Description **Generate dependency graph**
// @Description
// @Description Command graph generates graph of dependencies:
// @Description
// @Description * between snapshots and mirrors (what mirror was used to create each snapshot)
// @Description * between snapshots and local repos (what local repo was used to create snapshot)
// @Description * between snapshots (pulling, merging, etc.)
// @Description * between snapshots, local repos and published repositories (how snapshots were published).
// @Description
// @Description Graph is rendered to PNG file using graphviz package.
// @Description
// @Description Example URL: `http://localhost:8080/api/graph.svg?layout=vertical`
// @Tags Status
// @Produce image/png, image/svg+xml
// @Param ext path string true "ext specifies desired file extension, e.g. .png, .svg."
// @Param layout query string false "Change between a `horizontal` (default) and a `vertical` graph layout."
// @Success 200 {object} []byte "Output"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/graph.{ext} [get]
func apiGraph(c *gin.Context) {
var (
err error
+12 -6
View File
@@ -31,7 +31,7 @@ func getVerifier(keyRings []string) (pgp.Verifier, error) {
return verifier, nil
}
// @Summary Get mirrors
// @Summary List Mirrors
// @Description **Show list of currently available mirrors**
// @Description Each mirror is returned as in “show” API.
// @Tags Mirrors
@@ -82,8 +82,8 @@ type mirrorCreateParams struct {
IgnoreSignatures bool ` json:"IgnoreSignatures"`
}
// @Summary Create mirror
// @Description **Create a mirror**
// @Summary Create Mirror
// @Description **Create a mirror of a remote repository**
// @Tags Mirrors
// @Consume json
// @Param request body mirrorCreateParams true "Parameters"
@@ -164,6 +164,7 @@ func apiMirrorsCreate(c *gin.Context) {
// @Tags Mirrors
// @Param name path string true "mirror name"
// @Param force query int true "force: 1 to enable"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} task.ProcessReturnValue
// @Failure 404 {object} Error "Mirror not found"
@@ -208,7 +209,7 @@ func apiMirrorsDrop(c *gin.Context) {
})
}
// @Summary Show Mirror
// @Summary Get Mirror Info
// @Description **Get mirror information by name**
// @Tags Mirrors
// @Param name path string true "mirror name"
@@ -306,8 +307,12 @@ func apiMirrorsPackages(c *gin.Context) {
list.PrepareIndex()
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
nil, context.DependencyOptions(), architecturesList)
list, err = list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{q},
WithDependencies: withDeps,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
})
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err))
}
@@ -364,6 +369,7 @@ type mirrorUpdateParams struct {
// @Param name path string true "mirror name to update"
// @Consume json
// @Param request body mirrorUpdateParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} task.ProcessReturnValue "Mirror was updated successfully"
// @Success 202 {object} task.Task "Mirror is being updated"
+12 -3
View File
@@ -1,10 +1,19 @@
package api
import (
_ "github.com/aptly-dev/aptly/deb" // for swagger
"github.com/gin-gonic/gin"
)
// GET /api/packages/:key
// @Summary Get Package Info
// @Description **Show information about package by package key**
// @Description Package keys could be obtained from various GET .../packages APIs.
// @Tags Packages
// @Produce json
// @Param key path string true "package key (unique package identifier)"
// @Success 200 {object} deb.Package "OK"
// @Failure 404 {object} Error "Not Found"
// @Router /api/packages/{key} [get]
func apiPackagesShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
p, err := collectionFactory.PackageCollection().ByKey([]byte(c.Params.ByName("key")))
@@ -16,8 +25,8 @@ func apiPackagesShow(c *gin.Context) {
c.JSON(200, p)
}
// @Summary Get packages
// @Description Get list of packages.
// @Summary List Packages
// @Description **Get list of packages**
// @Tags Packages
// @Consume json
// @Produce json
+18 -3
View File
@@ -179,9 +179,16 @@ type publishedRepoCreateParams struct {
// @Description
// @Description The prefix may contain a storage specifier, e.g. `s3:packages/`, or it may also be empty to publish to the root directory.
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Label":"","Origin":"","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
// @Description ```
// @Description
// @Description See also: `aptly publish create`
// @Tags Publish
// @Param prefix path string true "publishing prefix"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body publishedRepoCreateParams true "Parameters"
// @Produce json
@@ -389,6 +396,7 @@ type publishedRepoUpdateSwitchParams struct {
// @Produce json
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body publishedRepoUpdateSwitchParams true "Parameters"
// @Produce json
@@ -519,6 +527,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
// @Param distribution path string true "distribution name"
// @Param force query int true "force: 1 to enable"
// @Param skipCleanup query int true "skipCleanup: 1 to enable"
// @Param _async query bool false "Run in background and return task object"
// @Success 200
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Published repository not found"
@@ -565,6 +574,7 @@ func apiPublishDrop(c *gin.Context) {
// @Tags Publish
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body sourceParams true "Parameters"
// @Produce json
@@ -625,7 +635,7 @@ func apiPublishAddSource(c *gin.Context) {
})
}
// @Summary List pending changes
// @Summary List Pending Changes
// @Description **List source component changes to be applied**
// @Description
// @Description Return added, removed or changed components of snapshots or local repository to be published.
@@ -682,6 +692,7 @@ func apiPublishListChanges(c *gin.Context) {
// @Tags Publish
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body []sourceParams true "Parameters"
// @Produce json
@@ -738,8 +749,8 @@ func apiPublishSetSources(c *gin.Context) {
})
}
// @Summary Drop pending changes
// @Description **Drop pending source component changes of a published repository**
// @Summary Discard Pending Changes
// @Description **Discard pending source component changes of a published repository**
// @Description
// @Description Remove all pending changes what would be applied with a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
// @Description
@@ -747,6 +758,7 @@ func apiPublishSetSources(c *gin.Context) {
// @Tags Publish
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200
// @Failure 400 {object} Error "Bad Request"
@@ -799,6 +811,7 @@ func apiPublishDropChanges(c *gin.Context) {
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param component path string true "component name"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body sourceParams true "Parameters"
// @Produce json
@@ -878,6 +891,7 @@ func apiPublishUpdateSource(c *gin.Context) {
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param component path string true "component name"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200
// @Failure 400 {object} Error "Bad Request"
@@ -954,6 +968,7 @@ type publishedRepoUpdateParams struct {
// @Tags Publish
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body publishedRepoUpdateParams true "Parameters"
// @Produce json
+200 -35
View File
@@ -18,7 +18,13 @@ import (
"github.com/gin-gonic/gin"
)
// GET /repos
// @Summary Serve HTML Listing
// @Description If ServeInAPIMode is enabled in aptly config,
// @Description this endpoint is enabled which returns an HTML listing of each repo that can be browsed
// @Tags Repos
// @Produce html
// @Success 200 {object} string "HTML"
// @Router /api/repos [get]
func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -35,7 +41,15 @@ func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.H
}
}
// GET /repos/:storage/*pkgPath
// @Summary Serve Packages
// @Description If ServeInAPIMode is enabled in aptly config,
// @Description this api serves a specified package from storage
// @Tags Repos
// @Param storage path string true "Storage"
// @Param pkgPath path string true "Package Path" allowReserved=true
// @Produce json
// @Success 200 ""
// @Router /api/{storage}/{pkgPath} [get]
func reposServeInAPIMode(c *gin.Context) {
pkgpath := c.Param("pkgPath")
@@ -50,8 +64,9 @@ func reposServeInAPIMode(c *gin.Context) {
c.FileFromFS(pkgpath, http.Dir(publicPath))
}
// @Summary Get repos
// @Description Get list of available repos. Each repo is returned as in “show” API.
// @Summary List Repositories
// @Description **Get list of available repos**
// @Description Each repo is returned as in “show” API.
// @Tags Repos
// @Produce json
// @Success 200 {array} deb.LocalRepo
@@ -82,8 +97,15 @@ type repoCreateParams struct {
FromSnapshot string ` json:"FromSnapshot" example:"snapshot1"`
}
// @Summary Create repository
// @Description Create a local repository.
// @Summary Create Repository
// @Description **Create a local repository**
// @Description
// @Description Distribution and component are used as defaults when publishing repo either directly or via snapshot.
// @Description
// @Description ```
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Name": "aptly-repo"}' http://localhost:8080/api/repos
// @Description {"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}
// @Description ```
// @Tags Repos
// @Produce json
// @Consume json
@@ -142,15 +164,28 @@ func apiReposCreate(c *gin.Context) {
c.JSON(http.StatusCreated, repo)
}
// PUT /api/repos/:name
func apiReposEdit(c *gin.Context) {
var b struct {
Name *string
Comment *string
DefaultDistribution *string
DefaultComponent *string
}
type reposEditParams struct {
// Name of repository to modify
Name *string `binding:"required" json:"Name" example:"repo1"`
// Change Comment of repository
Comment *string ` json:"Comment" example:"example repo"`
// Change Default Distribution for publishing
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
// Change Devault Component for publishing
DefaultComponent *string ` json:"DefaultComponent" example:""`
}
// @Summary Update Repository
// @Description **Update local repository meta information**
// @Tags Repos
// @Produce json
// @Param request body reposEditParams true "Parameters"
// @Success 200 {object} deb.LocalRepo "msg"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/repos/{name} [put]
func apiReposEdit(c *gin.Context) {
var b reposEditParams
if c.Bind(&b) != nil {
return
}
@@ -193,7 +228,7 @@ func apiReposEdit(c *gin.Context) {
}
// GET /api/repos/:name
// @Summary Get repository info by name
// @Summary Get Repository Info
// @Description Returns basic information about local repository.
// @Tags Repos
// @Produce json
@@ -214,7 +249,18 @@ func apiReposShow(c *gin.Context) {
c.JSON(200, repo)
}
// DELETE /api/repos/:name
// @Summary Delete Repository
// @Description Drop/delete a repo
// @Description Cannot drop repos that are published.
// @Description Needs force=1 to drop repos used as source by other repos.
// @Tags Repos
// @Produce json
// @Param _async query bool false "Run in background and return task object"
// @Param force query int false "force: 1 to enable"
// @Success 200 {object} task.ProcessReturnValue "Repo object"
// @Failure 404 {object} Error "Not Found"
// @Failure 404 {object} Error "Repo Conflict"
// @Router /api/repos/{name} [delete]
func apiReposDrop(c *gin.Context) {
force := c.Request.URL.Query().Get("force") == "1"
name := c.Params.ByName("name")
@@ -249,7 +295,27 @@ func apiReposDrop(c *gin.Context) {
})
}
// GET /api/repos/:name/packages
// @Summary List Repo Packages
// @Description **Return a list of packages present in the repo**
// @Description
// @Description If `q` query parameter is missing, return all packages, otherwise return packages that match q
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/repos/aptly-repo/packages
// @Description ["Pi386 aptly 0.8 966561016b44ed80"]
// @Description ```
// @Tags Repos
// @Produce json
// @Param name path string true "Snapshot to search"
// @Param q query string true "Package query (e.g Name%20(~%20matlab))"
// @Param withDeps query string true "Set to 1 to include dependencies when evaluating package query"
// @Param format query string true "Set to 'details' to return extra info about each package"
// @Param maximumVersion query string true "Set to 1 to only return the highest version for each package name"
// @Success 200 {object} string "msg"
// @Failure 404 {object} Error "Not Found"
// @Failure 404 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/packages [get]
func apiReposPackagesShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
@@ -269,11 +335,14 @@ func apiReposPackagesShow(c *gin.Context) {
showPackages(c, repo.RefList(), collectionFactory)
}
type reposPackagesAddDeleteParams struct {
// Package Refs
PackageRefs []string `binding:"required" json:"PackageRefs" example:""`
}
// Handler for both add and delete
func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error) {
var b struct {
PackageRefs []string
}
var b reposPackagesAddDeleteParams
if c.Bind(&b) != nil {
return
@@ -330,7 +399,21 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
})
}
// POST /repos/:name/packages
// @Summary Add Packages by Key
// @Description **Add packages to local repository by package keys.**
// @Description
// @Description Any package can be added that is present in the aptly database (from any mirror, snapshot, local repository). This API combined with package list (search) APIs allows one to implement importing, copying, moving packages around.
// @Description
// @Description API verifies that packages actually exist in aptly database and checks constraint that conflicting packages cant be part of the same local repository.
// @Tags Repos
// @Produce json
// @Param request body reposPackagesAddDeleteParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} string "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 400 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/packages [post]
func apiReposPackagesAdd(c *gin.Context) {
apiReposPackagesAddDelete(c, "Add packages to repo ", func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error {
out.Printf("Adding package %s\n", p.Name)
@@ -338,7 +421,19 @@ func apiReposPackagesAdd(c *gin.Context) {
})
}
// DELETE /repos/:name/packages
// @Summary Delete Packages by Key
// @Description **Remove packages from local repository by package keys.**
// @Description
// @Description Any package(s) can be removed from a local repository. Package references from a local repository can be retrieved with GET /api/repos/:name/packages.
// @Tags Repos
// @Produce json
// @Param request body reposPackagesAddDeleteParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} string "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 400 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/packages [delete]
func apiReposPackagesDelete(c *gin.Context) {
apiReposPackagesAddDelete(c, "Delete packages from repo ", func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error {
out.Printf("Removing package %s\n", p.Name)
@@ -347,13 +442,27 @@ func apiReposPackagesDelete(c *gin.Context) {
})
}
// POST /repos/:name/file/:dir/:file
// @Summary Add Uploaded File
// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically.
// @Description Adding same package to local repository is not an error.
// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import).
// @Tags Repos
// @Param name path string true "Repository name"
// @Param dir path string true "Directory of packages"
// @Param file path string false "Filename (optional)"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {string} string "OK"
// @Failure 400 {object} Error "wrong file"
// @Failure 404 {object} Error "Repository not found"
// @Failure 500 {object} Error "Error adding files"
// @Router /api/repos/{name}/file/{dir}/{file} [post]
func apiReposPackageFromFile(c *gin.Context) {
// redirect all work to dir method
apiReposPackageFromDir(c)
}
// @Summary Add packages from uploaded file/directory
// @Summary Add Uploaded Directory
// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically.
// @Description Adding same package to local repository is not an error.
// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import).
@@ -363,12 +472,13 @@ func apiReposPackageFromFile(c *gin.Context) {
// @Consume json
// @Param noRemove query string false "when value is set to 1, dont remove any files"
// @Param forceReplace query string false "when value is set to 1, remove packages conflicting with package being added (in local repository)"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {string} string "OK"
// @Failure 400 {object} Error "wrong file"
// @Failure 404 {object} Error "Repository not found"
// @Failure 500 {object} Error "Error adding files"
// @Router /api/repos/{name}/{dir} [post]
// @Router /api/repos/{name}/file/{dir} [post]
func apiReposPackageFromDir(c *gin.Context) {
forceReplace := c.Request.URL.Query().Get("forceReplace") == "1"
noRemove := c.Request.URL.Query().Get("noRemove") == "1"
@@ -487,16 +597,33 @@ func apiReposPackageFromDir(c *gin.Context) {
})
}
// POST /repos/:name/copy/:src/:file
type reposCopyPackageParams struct {
// Copy also dependencies
WithDeps bool `json:"with-deps,omitempty"`
// Do not perform operations
DryRun bool `json:"dry-run,omitempty"`
}
// @Summary Copy Package
// @Description Copies a package from a source to destination repository
// @Tags Repos
// @Produce json
// @Param name path string true "Source repo"
// @Param src path string true "Destination repo"
// @Param file path string true "File/packages to copy"
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} task.ProcessReturnValue "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 422 {object} Error "Unprocessable Entity"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/copy/{src}/{file} [post]
func apiReposCopyPackage(c *gin.Context) {
dstRepoName := c.Params.ByName("name")
srcRepoName := c.Params.ByName("src")
fileName := c.Params.ByName("file")
jsonBody := struct {
WithDeps bool `json:"with-deps,omitempty"`
DryRun bool `json:"dry-run,omitempty"`
}{
jsonBody := reposCopyPackageParams{
WithDeps: false,
DryRun: false,
}
@@ -550,7 +677,6 @@ func apiReposCopyPackage(c *gin.Context) {
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())
@@ -586,7 +712,14 @@ func apiReposCopyPackage(c *gin.Context) {
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())
toProcess, err := srcList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: jsonBody.WithDeps,
Source: dstList,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("filter error: %s", err)
}
@@ -626,13 +759,46 @@ func apiReposCopyPackage(c *gin.Context) {
})
}
// POST /repos/:name/include/:dir/:file
// @Summary Include File from Directory
// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api.
// @Tags Repos
// @Produce json
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
// @Param noRemoveFiles query int false "when value is set to 1, dont remove files that have been imported successfully into repository"
// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files"
// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature"
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} string "msg"
// @Failure 404 {object} Error "Not Found"
// @Router /api/repos/{name}/include/{dir}/{file} [post]
func apiReposIncludePackageFromFile(c *gin.Context) {
// redirect all work to dir method
apiReposIncludePackageFromDir(c)
}
// POST /repos/:name/include/:dir
type reposIncludePackageFromDirReport struct {
Warnings []string
Added []string
Deleted []string
}
type reposIncludePackageFromDirResponse struct {
Report reposIncludePackageFromDirReport
FailedFiles []string
}
// @Summary Include Directory
// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api.
// @Tags Repos
// @Produce json
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
// @Param noRemoveFiles query int false "when value is set to 1, dont remove files that have been imported successfully into repository"
// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files"
// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature"
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} reposIncludePackageFromDirResponse "Response"
// @Failure 404 {object} Error "Not Found"
// @Router /api/repos/{name}/include/{dir} [post]
func apiReposIncludePackageFromDir(c *gin.Context) {
forceReplace := c.Request.URL.Query().Get("forceReplace") == "1"
noRemoveFiles := c.Request.URL.Query().Get("noRemoveFiles") == "1"
@@ -734,6 +900,5 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
"Report": reporter,
"FailedFiles": failedFiles,
}}, nil
})
}
+13 -15
View File
@@ -12,7 +12,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log"
_ "github.com/aptly-dev/aptly/docs" // import docs
"github.com/aptly-dev/aptly/docs"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
@@ -27,26 +27,22 @@ func apiMetricsGet() gin.HandlerFunc {
}
func redirectSwagger(c *gin.Context) {
if c.Request.URL.Path == "/docs/index.html" {
c.Redirect(http.StatusMovedPermanently, "/docs.html")
return
}
if c.Request.URL.Path == "/docs/" {
c.Redirect(http.StatusMovedPermanently, "/docs/index.html")
c.Redirect(http.StatusMovedPermanently, "/docs.html")
return
}
if c.Request.URL.Path == "/docs" {
c.Redirect(http.StatusMovedPermanently, "/docs.html")
return
}
c.Next()
}
// Router returns prebuilt with routes http.Handler
// @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 {
if aptly.EnableDebug {
gin.SetMode(gin.DebugMode)
@@ -73,6 +69,9 @@ func Router(c *ctx.AptlyContext) http.Handler {
router.Use(gin.Recovery(), gin.ErrorLogger())
if c.Config().EnableSwaggerEndpoint {
router.GET("docs.html", func(c *gin.Context) {
c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
})
router.Use(redirectSwagger)
url := ginSwagger.URL("/docs/doc.json")
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
@@ -233,7 +232,6 @@ func Router(c *ctx.AptlyContext) http.Handler {
api.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
api.GET("/tasks/:id", apiTasksShow)
api.DELETE("/tasks/:id", apiTasksDelete)
api.POST("/tasks-dummy", apiTasksDummy)
}
return router
+6 -4
View File
@@ -4,10 +4,12 @@ import (
"github.com/gin-gonic/gin"
)
// @Summary Get S3 buckets
// @Description Get list of S3 buckets.
// @Tags S3
// @Produce json
// @Summary S3 buckets
// @Description **Get list of S3 buckets**
// @Description
// @Description List configured S3 buckets.
// @Tags Status
// @Produce json
// @Success 200 {array} string "List of S3 buckets"
// @Router /api/s3 [get]
func apiS3List(c *gin.Context) {
+157 -38
View File
@@ -14,8 +14,10 @@ import (
"github.com/gin-gonic/gin"
)
// @Summary Get snapshots
// @Description Get list of available snapshots. Each snapshot is returned as in “show” API.
// @Summary List Snapshots
// @Description **Get list of snapshots**
// @Description
// @Description Each snapshot is returned as in “show” API.
// @Tags Snapshots
// @Produce json
// @Success 200 {array} deb.Snapshot
@@ -39,19 +41,34 @@ func apiSnapshotsList(c *gin.Context) {
c.JSON(200, result)
}
// POST /api/mirrors/:name/snapshots/
type snapshotsCreateFromMirrorParams struct {
// Name of snapshot to create
Name string `binding:"required" json:"Name" example:"snap1"`
// Description of snapshot
Description string ` json:"Description"`
}
// @Summary Snapshot Mirror
// @Description **Create a snapshot of a mirror**
// @Tags Snapshots
// @Produce json
// @Param request body snapshotsCreateFromMirrorParams true "Parameters"
// @Param name path string true "Mirror name"
// @Param _async query bool false "Run in background and return task object"
// @Success 201 {object} deb.Snapshot "Created Snapshot"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Mirror Not Found"
// @Failure 409 {object} Error "Conflicting snapshot"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/mirrors/{name}/snapshots [post]
func apiSnapshotsCreateFromMirror(c *gin.Context) {
var (
err error
repo *deb.RemoteRepo
snapshot *deb.Snapshot
b snapshotsCreateFromMirrorParams
)
var b struct {
Name string `binding:"required"`
Description string
}
if c.Bind(&b) != nil {
return
}
@@ -98,20 +115,37 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
})
}
// POST /api/snapshots
type snapshotsCreateParams struct {
// Name of snapshot to create
Name string `binding:"required" json:"Name" example:"snap2"`
// Description of snapshot
Description string ` json:"Description"`
// List of source snapshots
SourceSnapshots []string ` json:"SourceSnapshots" example:"snap1"`
// List of package refs
PackageRefs []string ` json:"PackageRefs" example:""`
}
// @Summary Snapshot Packages
// @Description **Create a snapshot from package refs**
// @Description
// @Description Refs can be obtained from snapshots, local repos, or mirrors
// @Tags Snapshots
// @Param request body snapshotsCreateParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 201 {object} deb.Snapshot "Created snapshot"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Source snapshot or package refs not found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots [post]
func apiSnapshotsCreate(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
b snapshotsCreateParams
)
var b struct {
Name string `binding:"required"`
Description string
SourceSnapshots []string
PackageRefs []string
}
if c.Bind(&b) != nil {
return
}
@@ -173,19 +207,35 @@ func apiSnapshotsCreate(c *gin.Context) {
})
}
// POST /api/repos/:name/snapshots
type snapshotsCreateFromRepositoryParams struct {
// Name of snapshot to create
Name string `binding:"required" json:"Name" example:"snap1"`
// Description of snapshot
Description string ` json:"Description"`
}
// @Summary Snapshot Repository
// @Description **Create a snapshot of a repository by name**
// @Tags Snapshots
// @Param name path string true "Repository name"
// @Consume json
// @Param request body snapshotsCreateFromRepositoryParams true "Parameters"
// @Param name path string true "Name of the snapshot"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 201 {object} deb.Snapshot "Created snapshot object"
// @Failure 400 {object} Error "Bad Request"
// @Failure 500 {object} Error "Internal Server Error"
// @Failure 404 {object} Error "Repo Not Found"
// @Router /api/repos/{name}/snapshots [post]
func apiSnapshotsCreateFromRepository(c *gin.Context) {
var (
err error
repo *deb.LocalRepo
snapshot *deb.Snapshot
b snapshotsCreateFromRepositoryParams
)
var b struct {
Name string `binding:"required"`
Description string
}
if c.Bind(&b) != nil {
return
}
@@ -227,18 +277,32 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
})
}
// PUT /api/snapshots/:name
type snapshotsUpdateParams struct {
// Change Name of snapshot
Name string ` json:"Name" example:"snap2"`
// Change Description of snapshot
Description string `json:"Description"`
}
// @Summary Update Snapshot
// @Description **Update snapshot metadata (Name, Description)**
// @Tags Snapshots
// @Param request body snapshotsUpdateParams true "Parameters"
// @Param name path string true "Snapshot name"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} deb.Snapshot "Updated snapshot object"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 409 {object} Error "Conflicting snapshot"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name} [put]
func apiSnapshotsUpdate(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
b snapshotsUpdateParams
)
var b struct {
Name string
Description string
}
if c.Bind(&b) != nil {
return
}
@@ -277,7 +341,15 @@ func apiSnapshotsUpdate(c *gin.Context) {
})
}
// GET /api/snapshots/:name
// @Summary Get Snapshot Info
// @Description **Query detailed information about a snapshot by name**
// @Tags Snapshots
// @Param name path string true "Name of the snapshot"
// @Produce json
// @Success 200 {object} deb.Snapshot "msg"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name} [get]
func apiSnapshotsShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
@@ -297,7 +369,20 @@ func apiSnapshotsShow(c *gin.Context) {
c.JSON(200, snapshot)
}
// DELETE /api/snapshots/:name
// @Summary Delete Snapshot
// @Description **Delete snapshot by name**
// @Description Cannot drop snapshots that are published.
// @Description Needs force=1 to drop snapshots used as source by other snapshots.
// @Tags Snapshots
// @Param name path string true "Snapshot name"
// @Param force query string false "Force operation"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 ""
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 409 {object} Error "Snapshot in use"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name} [delete]
func apiSnapshotsDrop(c *gin.Context) {
name := c.Params.ByName("name")
force := c.Request.URL.Query().Get("force") == "1"
@@ -336,7 +421,19 @@ func apiSnapshotsDrop(c *gin.Context) {
})
}
// GET /api/snapshots/:name/diff/:withSnapshot
// @Summary Snapshot diff
// @Description **Return the diff between two snapshots (name & withSnapshot)**
// @Description Provide `onlyMatching=1` to return only packages present in both snapshots.
// @Description Otherwise, returns a `left` and `right` result providing packages only in the first and second snapshots
// @Tags Snapshots
// @Produce json
// @Param name path string true "Snapshot name"
// @Param withSnapshot path string true "Snapshot name to diff against"
// @Param onlyMatching query string false "Only return packages present in both snapshots"
// @Success 200 {array} deb.PackageDiff "Package Diff"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name}/diff/{withSnapshot} [get]
func apiSnapshotsDiff(c *gin.Context) {
onlyMatching := c.Request.URL.Query().Get("onlyMatching") == "1"
@@ -387,7 +484,20 @@ func apiSnapshotsDiff(c *gin.Context) {
c.JSON(200, result)
}
// GET /api/snapshots/:name/packages
// @Summary List Snapshot Packages
// @Description **List all packages in snapshot or perform search on snapshot contents and return results**
// @Description If `q` query parameter is missing, return all packages, otherwise return packages that match q
// @Tags Snapshots
// @Produce json
// @Param name path string true "Snapshot to search"
// @Param q query string false "Package query (e.g Name%20(~%20matlab))"
// @Param withDeps query string false "Set to 1 to include dependencies when evaluating package query"
// @Param format query string false "Set to 'details' to return extra info about each package"
// @Param maximumVersion query string false "Set to 1 to only return the highest version for each package name"
// @Success 200 {array} string "Package info"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name}/packages [get]
func apiSnapshotsSearchPackages(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
@@ -419,13 +529,14 @@ type snapshotsMergeParams struct {
// @Description
// @Description If only one snapshot is specified, merge copies source into destination.
// @Tags Snapshots
// @Consume json
// @Produce json
// @Param name path string true "Name of the snapshot to be created"
// @Param latest query int false "merge only the latest version of each package"
// @Param no-remove query int false "all versions of packages are preserved during merge"
// @Consume json
// @Param request body snapshotsMergeParams true "Parameters"
// @Produce json
// @Success 200
// @Param _async query bool false "Run in background and return task object"
// @Success 201 {object} deb.Snapshot "Resulting snapshot object"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Error"
@@ -530,15 +641,16 @@ type snapshotsPullParams struct {
// @Description
// @Description Aptly pulls first package matching each of package queries, but with flag -all-matches all matching packages would be pulled.
// @Tags Snapshots
// @Param request body snapshotsPullParams true "Parameters"
// @Param name path string true "Name of the snapshot to be created"
// @Param all-matches query int false "pull all the packages that satisfy the dependency version requirements (default is to pull first matching package): 1 to enable"
// @Param dry-run query int false "dont create destination snapshot, just show what would be pulled: 1 to enable"
// @Param no-deps query int false "dont process dependencies, just pull listed packages: 1 to enable"
// @Param no-remove query int false "dont remove other package versions when pulling package: 1 to enable"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body snapshotsPullParams true "Parameters"
// @Produce json
// @Success 200
// @Success 200 {object} deb.Snapshot "Resulting Snapshot object"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Error"
@@ -636,7 +748,14 @@ func apiSnapshotsPull(c *gin.Context) {
}
// Filter with dependencies as requested
destinationPackageList, err := sourcePackageList.FilterWithProgress(queries, !noDeps, toPackageList, context.DependencyOptions(), architecturesList, context.Progress())
destinationPackageList, err := sourcePackageList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: !noDeps,
Source: toPackageList,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
+2
View File
@@ -18,6 +18,8 @@ type diskFree struct {
// @Summary Get Storage Utilization
// @Description **Get disk free information of aptly storage**
// @Description
// @Description Units in MiB.
// @Tags Status
// @Produce json
// @Success 200 {object} diskFree "Storage information"
+69 -25
View File
@@ -1,18 +1,16 @@
package api
import (
"net/http"
"strconv"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/task"
"github.com/gin-gonic/gin"
)
// @Summary Get tasks
// @Description Get list of available tasks. Each task is returned as in “show” API.
// @Summary List Tasks
// @Description **Get list of available tasks. Each task is returned as in “show” API**
// @Tags Tasks
// @Produce json
// @Produce json
// @Success 200 {array} task.Task
// @Router /api/tasks [get]
func apiTasksList(c *gin.Context) {
@@ -20,21 +18,39 @@ func apiTasksList(c *gin.Context) {
c.JSON(200, list.GetTasks())
}
// POST /tasks-clear
// @Summary Clear Tasks
// @Description **Removes finished and failed tasks from internal task list**
// @Tags Tasks
// @Produce json
// @Success 200 ""
// @Router /api/tasks-clear [post]
func apiTasksClear(c *gin.Context) {
list := context.TaskList()
list.Clear()
c.JSON(200, gin.H{})
}
// GET /tasks-wait
// @Summary Wait for all Tasks
// @Description **Waits for and returns when all running tasks are complete**
// @Tags Tasks
// @Produce json
// @Success 200 ""
// @Router /api/tasks-wait [get]
func apiTasksWait(c *gin.Context) {
list := context.TaskList()
list.Wait()
c.JSON(200, gin.H{})
}
// GET /tasks/:id/wait
// @Summary Wait for Task
// @Description **Waits for and returns when given Task ID is complete**
// @Tags Tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} task.Task
// @Failure 500 {object} Error "invalid syntax, bad id?"
// @Failure 400 {object} Error "Task Not Found"
// @Router /api/tasks/{id}/wait [get]
func apiTasksWaitForTaskByID(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
@@ -52,7 +68,15 @@ func apiTasksWaitForTaskByID(c *gin.Context) {
c.JSON(200, task)
}
// GET /tasks/:id
// @Summary Get Task Info
// @Description **Return task information for a given ID**
// @Tags Tasks
// @Produce plain
// @Param id path int true "Task ID"
// @Success 200 {object} task.Task
// @Failure 500 {object} Error "invalid syntax, bad id?"
// @Failure 404 {object} Error "Task Not Found"
// @Router /api/tasks/{id} [get]
func apiTasksShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
@@ -71,7 +95,15 @@ func apiTasksShow(c *gin.Context) {
c.JSON(200, task)
}
// GET /tasks/:id/output
// @Summary Get Task Output
// @Description **Return task output for a given ID**
// @Tags Tasks
// @Produce plain
// @Param id path int true "Task ID"
// @Success 200 {object} string "Task output"
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 404 {object} Error "Task Not Found"
// @Router /api/tasks/{id}/output [get]
func apiTasksOutputShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
@@ -90,7 +122,15 @@ func apiTasksOutputShow(c *gin.Context) {
c.JSON(200, output)
}
// GET /tasks/:id/detail
// @Summary Get Task Details
// @Description **Return task detail for a given ID**
// @Tags Tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} string "Task detail"
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 404 {object} Error "Task Not Found"
// @Router /api/tasks/{id}/detail [get]
func apiTasksDetailShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
@@ -109,7 +149,15 @@ func apiTasksDetailShow(c *gin.Context) {
c.JSON(200, detail)
}
// GET /tasks/:id/return_value
// @Summary Get Task Return Value
// @Description **Return task return value (status code) by given ID**
// @Tags Tasks
// @Produce plain
// @Param id path int true "Task ID"
// @Success 200 {object} string "msg"
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 404 {object} Error "Not Found"
// @Router /api/tasks/{id}/return_value [get]
func apiTasksReturnValueShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
@@ -127,7 +175,15 @@ func apiTasksReturnValueShow(c *gin.Context) {
c.JSON(200, output)
}
// DELETE /tasks/:id
// @Summary Delete Task
// @Description **Delete completed task by given ID. Does not stop task execution**
// @Tags Tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} task.Task
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 400 {object} Error "Task in progress or not found"
// @Router /api/tasks/{id} [delete]
func apiTasksDelete(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
@@ -145,15 +201,3 @@ func apiTasksDelete(c *gin.Context) {
c.JSON(200, delTask)
}
// POST /tasks-dummy
func apiTasksDummy(c *gin.Context) {
resources := []string{"dummy"}
taskName := "Dummy task"
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
out.Printf("Dummy task started\n")
detail.Store([]int{1, 2, 3})
out.Printf("Dummy task finished\n")
return &task.ProcessReturnValue{Code: http.StatusTeapot, Value: []int{1, 2, 3}}, nil
})
}
-75
View File
@@ -1,75 +0,0 @@
package api
import (
"encoding/json"
"fmt"
"time"
"github.com/aptly-dev/aptly/task"
. "gopkg.in/check.v1"
)
type TaskSuite struct {
ApiSuite
}
var _ = Suite(&TaskSuite{})
func (s *TaskSuite) TestTasksDummy(c *C) {
response, _ := s.HTTPRequest("POST", "/api/tasks-dummy", nil)
c.Check(response.Code, Equals, 418)
c.Check(response.Body.String(), Equals, "[1,2,3]")
}
func (s *TaskSuite) TestTasksDummyAsync(c *C) {
response, _ := s.HTTPRequest("POST", "/api/tasks-dummy?_async=true", nil)
c.Check(response.Code, Equals, 202)
var t task.Task
err := json.Unmarshal(response.Body.Bytes(), &t)
c.Assert(err, IsNil)
c.Check(t.Name, Equals, "Dummy task")
response, _ = s.HTTPRequest("GET", fmt.Sprintf("/api/tasks/%d/wait", t.ID), nil)
err = json.Unmarshal(response.Body.Bytes(), &t)
c.Assert(err, IsNil)
c.Check(t.State, Equals, task.SUCCEEDED)
response, _ = s.HTTPRequest("GET", fmt.Sprintf("/api/tasks/%d/detail", t.ID), nil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Equals, "[1,2,3]")
response, _ = s.HTTPRequest("GET", fmt.Sprintf("/api/tasks/%d/output", t.ID), nil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, "\"Dummy task started.*")
}
func (s *TaskSuite) TestTaskDelete(c *C) {
response, _ := s.HTTPRequest("POST", "/api/tasks-dummy?_async=true", nil)
c.Check(response.Code, Equals, 202)
c.Check(response.Body.String(), Equals, "{\"Name\":\"Dummy task\",\"ID\":1,\"State\":0}")
// Give the task time to start
time.Sleep(time.Second)
response, _ = s.HTTPRequest("DELETE", "/api/tasks/1", nil)
c.Check(response.Code, Equals, 200)
}
func (s *TaskSuite) TestTasksClear(c *C) {
response, _ := s.HTTPRequest("POST", "/api/tasks-dummy?_async=true", nil)
c.Check(response.Code, Equals, 202)
var t task.Task
err := json.Unmarshal(response.Body.Bytes(), &t)
c.Assert(err, IsNil)
c.Check(t.Name, Equals, "Dummy task")
response, _ = s.HTTPRequest("GET", "/api/tasks-wait", nil)
c.Check(response.Code, Equals, 200)
response, _ = s.HTTPRequest("GET", "/api/tasks", nil)
c.Check(response.Code, Equals, 200)
var ts []task.Task
err = json.Unmarshal(response.Body.Bytes(), &ts)
c.Assert(err, IsNil)
c.Check(len(ts), Equals, 1)
c.Check(ts[0].State, Equals, task.SUCCEEDED)
response, _ = s.HTTPRequest("POST", "/api/tasks-clear", nil)
c.Check(response.Code, Equals, 200)
response, _ = s.HTTPRequest("GET", "/api/tasks", nil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Equals, "[]")
}
+4
View File
@@ -0,0 +1,4 @@
package aptly
// Default aptly.conf (filled in at link time)
var AptlyConf []byte
+17 -5
View File
@@ -5,19 +5,30 @@ import (
"fmt"
"github.com/smira/commander"
"gopkg.in/yaml.v3"
)
func aptlyConfigShow(_ *commander.Command, _ []string) error {
showYaml := context.Flags().Lookup("yaml").Value.Get().(bool)
config := context.Config()
prettyJSON, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("unable to dump the config file: %s", err)
if showYaml {
yamlData, err := yaml.Marshal(&config)
if err != nil {
return fmt.Errorf("error marshaling to YAML: %s", err)
}
fmt.Println(string(yamlData))
} else {
prettyJSON, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("unable to dump the config file: %s", err)
}
fmt.Println(string(prettyJSON))
}
fmt.Println(string(prettyJSON))
return nil
}
@@ -35,5 +46,6 @@ Example:
`,
}
cmd.Flag.Bool("yaml", false, "show yaml config")
return cmd
}
+3 -15
View File
@@ -9,7 +9,6 @@ import (
"path/filepath"
"runtime"
"strings"
"time"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
@@ -79,10 +78,6 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
return err
}
defer func() {
_ = os.Remove(tempfilename)
}()
if output != "" {
err = utils.CopyFile(tempfilename, output)
if err != nil {
@@ -90,23 +85,16 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
}
fmt.Printf("Output saved to %s\n", output)
_ = os.Remove(tempfilename)
} else {
command := getOpenCommand()
fmt.Printf("Rendered to %s file: %s, trying to open it with: %s %s...\n", format, tempfilename, command, tempfilename)
fmt.Printf("Displaying %s file: %s %s\n", format, command, tempfilename)
args := strings.Split(command, " ")
viewer := exec.Command(args[0], append(args[1:], tempfilename)...)
viewer.Stderr = os.Stderr
if err = viewer.Start(); err == nil {
// Wait for a second so that the visualizer has a chance to
// open the input file. This needs to be done even if we're
// waiting for the visualizer as it can be just a wrapper that
// spawns a browser tab and returns right away.
defer func(t <-chan time.Time) {
<-t
}(time.After(time.Second))
}
err = viewer.Start()
}
return err
+1 -1
View File
@@ -91,7 +91,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
func makeCmdRepoAdd() *commander.Command {
cmd := &commander.Command{
Run: aptlyRepoAdd,
UsageLine: "add <name> <package file.deb>|<directory> ...",
UsageLine: "add <name> (<package file.deb>|<directory>)...",
Short: "add packages to local repository",
Long: `
Command adds packages to local repository from .deb, .udeb (binary packages) and .dsc (source packages) files.
+1 -1
View File
@@ -86,7 +86,7 @@ func aptlyRepoInclude(cmd *commander.Command, args []string) error {
func makeCmdRepoInclude() *commander.Command {
cmd := &commander.Command{
Run: aptlyRepoInclude,
UsageLine: "include <file.changes>|<directory> ...",
UsageLine: "include (<file.changes>|<directory>)...",
Short: "add packages to local repositories based on .changes files",
Long: `
Command include looks for .changes files in list of arguments or specified directories. Each
+8 -1
View File
@@ -116,7 +116,14 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
}
}
toProcess, err := srcList.FilterWithProgress(queries, withDeps, dstList, context.DependencyOptions(), architecturesList, context.Progress())
toProcess, err := srcList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: withDeps,
Source: dstList,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return fmt.Errorf("unable to %s: %s", command, err)
}
+1 -1
View File
@@ -45,7 +45,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
}
list.PrepareIndex()
toRemove, err := list.Filter(queries, false, nil, 0, nil)
toRemove, err := list.Filter(deb.FilterOptions{Queries: queries})
if err != nil {
return fmt.Errorf("unable to remove: %s", err)
}
+1 -1
View File
@@ -84,7 +84,7 @@ func aptlySnapshotCreate(cmd *commander.Command, args []string) error {
func makeCmdSnapshotCreate() *commander.Command {
cmd := &commander.Command{
Run: aptlySnapshotCreate,
UsageLine: "create <name> from mirror <mirror-name> | from repo <repo-name> | empty",
UsageLine: "create <name> (from mirror <mirror-name> | from repo <repo-name> | empty)",
Short: "creates snapshot of mirror (local repository) contents",
Long: `
Command create <name> from mirror makes persistent immutable snapshot of remote
+8 -1
View File
@@ -67,7 +67,14 @@ func aptlySnapshotFilter(cmd *commander.Command, args []string) error {
}
// Filter with dependencies as requested
result, err := packageList.FilterWithProgress(queries, withDeps, nil, context.DependencyOptions(), architecturesList, context.Progress())
result, err := packageList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: withDeps,
Source: nil,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return fmt.Errorf("unable to filter: %s", err)
}
+8 -1
View File
@@ -97,7 +97,14 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
}
// Filter with dependencies as requested
result, err := sourcePackageList.FilterWithProgress(queries, !noDeps, packageList, context.DependencyOptions(), architecturesList, context.Progress())
result, err := sourcePackageList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: !noDeps,
Source: packageList,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return fmt.Errorf("unable to pull: %s", err)
}
+7 -2
View File
@@ -103,8 +103,13 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
}
}
result, err := list.FilterWithProgress([]deb.PackageQuery{q}, withDeps,
nil, context.DependencyOptions(), architecturesList, context.Progress())
result, err := list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{q},
WithDependencies: withDeps,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return fmt.Errorf("unable to search: %s", err)
}
+1 -1
View File
@@ -131,7 +131,7 @@ func formatCommands(args []string) [][]string {
func makeCmdTaskRun() *commander.Command {
cmd := &commander.Command{
Run: aptlyTaskRun,
UsageLine: "run -filename=<filename> | <command1>, <command2>, ...",
UsageLine: "run (-filename=<filename> | <commands>...)",
Short: "run aptly tasks",
Long: `
Command helps organise multiple aptly commands in one single aptly task, running as single thread.
+8 -7
View File
@@ -3,7 +3,6 @@ package context
import (
gocontext "context"
"errors"
"fmt"
"math/rand"
"os"
@@ -116,9 +115,11 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
if err != nil {
fmt.Fprintf(os.Stderr, "Config file not found, creating default config at %s\n\n", homeLocation)
// as this is fresh aptly installation, we don't need to support legacy pool locations
utils.Config.SkipLegacyPool = true
utils.SaveConfig(configLocations[0], &utils.Config)
utils.SaveConfigRaw(homeLocation, aptly.AptlyConf)
err = utils.LoadConfig(homeLocation, &utils.Config)
if err != nil {
Fatal(fmt.Errorf("error loading config file %s: %s", homeLocation, err))
}
}
}
@@ -293,10 +294,10 @@ func (context *AptlyContext) _database() (database.Storage, error) {
var err error
switch context.config().DatabaseBackend.Type {
case "leveldb":
if len(context.config().DatabaseBackend.DbPath) == 0 {
return nil, errors.New("leveldb databaseBackend config invalid")
dbPath := filepath.Join(context.config().GetRootDir(), "db")
if len(context.config().DatabaseBackend.DbPath) != 0 {
dbPath = context.config().DatabaseBackend.DbPath
}
dbPath := filepath.Join(context.config().GetRootDir(), context.config().DatabaseBackend.DbPath)
context.database, err = goleveldb.NewDB(dbPath)
case "etcd":
context.database, err = etcddb.NewDB(context.config().DatabaseBackend.URL)
+4 -1
View File
@@ -1,6 +1,8 @@
package context
import (
"fmt"
"os"
"reflect"
"testing"
@@ -82,5 +84,6 @@ func (s *AptlyContextSuite) TestGetPublishedStorageBadFS(c *C) {
// storage never exists.
c.Assert(func() { s.context.GetPublishedStorage("filesystem:fuji") },
FatalErrorPanicMatches,
&FatalError{ReturnCode: 1, Message: "published local storage fuji not configured"})
&FatalError{ReturnCode: 1, Message: fmt.Sprintf("error loading config file %s/.aptly.conf: invalid yaml (EOF) or json (EOF)",
os.Getenv("HOME"))})
}
+71 -22
View File
@@ -2,6 +2,7 @@ package deb
import (
"fmt"
"regexp"
"sort"
"strings"
@@ -503,32 +504,80 @@ func (l *PackageList) Search(dep Dependency, allMatches bool, searchProvided boo
return
}
// Filter filters package index by specified queries (ORed together), possibly pulling dependencies
func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) {
return l.FilterWithProgress(queries, withDependencies, source, dependencyOptions, architecturesList, nil)
// FilterOptions specifies options for Filter()
type FilterOptions struct {
Queries []PackageQuery
WithDependencies bool
WithSources bool // Source packages corresponding to binary packages are included
Source *PackageList
DependencyOptions int
Architectures []string
Progress aptly.Progress // set to non-nil value to report progress
}
// FilterWithProgress filters package index by specified queries (ORed together), possibly pulling dependencies and displays progress
func (l *PackageList) FilterWithProgress(queries []PackageQuery, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string, progress aptly.Progress) (*PackageList, error) {
// SourceRegex is a regular expression to match source package names.
// > In a binary package control file [...], the source package name may be followed by a version number in
// > parentheses. This version number may be omitted [...] if it has the same value as the Version field of
// > the binary package in question.
// > [...]
// > Package names (both source and binary, see Package) must consist only of lower case letters (a-z),
// > digits (0-9), plus (+) and minus (-) signs, and periods (.).
// > They must be at least two characters long and must start with an alphanumeric character.
// -- https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-source
var SourceRegex = regexp.MustCompile(`^([a-z0-9][-+.a-z0-9]+)(?:\s+\(([^)]+)\))?$`)
// Filter filters package index by specified queries (ORed together), possibly pulling dependencies
func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
if !l.indexed {
panic("list not indexed, can't filter")
}
result := NewPackageList()
for _, query := range queries {
result.Append(query.Query(l))
for _, query := range options.Queries {
_ = result.Append(query.Query(l))
}
// The above loop already finds source packages that are named equal to their binary package, but we still need
// to account for those that are named differently.
if options.WithSources {
sourceQueries := make([]PackageQuery, 0)
for _, pkg := range result.packages {
if pkg.Source == "" {
continue
}
matches := SourceRegex.FindStringSubmatch(pkg.Source)
if matches == nil {
return nil, fmt.Errorf("invalid Source field: %s", pkg.Source)
}
sourceName := matches[1]
if sourceName == pkg.Name {
continue
}
sourceVersion := pkg.Version
if matches[2] != "" {
sourceVersion = matches[2]
}
sourceQueries = append(sourceQueries, &DependencyQuery{Dependency{
Pkg: sourceName,
Version: sourceVersion,
Relation: VersionEqual,
Architecture: ArchitectureSource,
}})
}
for _, query := range sourceQueries {
_ = result.Append(query.Query(l))
}
}
if withDependencies {
if options.WithDependencies {
added := result.Len()
result.PrepareIndex()
dependencySource := NewPackageList()
if source != nil {
dependencySource.Append(source)
if options.Source != nil {
_ = dependencySource.Append(options.Source)
}
dependencySource.Append(result)
_ = dependencySource.Append(result)
dependencySource.PrepareIndex()
// while some new dependencies were discovered
@@ -536,22 +585,22 @@ func (l *PackageList) FilterWithProgress(queries []PackageQuery, withDependencie
added = 0
// find missing dependencies
missing, err := result.VerifyDependencies(dependencyOptions, architecturesList, dependencySource, progress)
missing, err := result.VerifyDependencies(options.DependencyOptions, options.Architectures, dependencySource, options.Progress)
if err != nil {
return nil, err
}
// try to satisfy dependencies
for _, dep := range missing {
if dependencyOptions&DepFollowAllVariants == 0 {
if options.DependencyOptions&DepFollowAllVariants == 0 {
// dependency might have already been satisfied
// with packages already been added
//
// when follow-all-variants is enabled, we need to try to expand anyway,
// as even if dependency is satisfied now, there might be other ways to satisfy dependency
if result.Search(dep, false, true) != nil {
if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil {
progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
}
continue
}
@@ -564,19 +613,19 @@ func (l *PackageList) FilterWithProgress(queries []PackageQuery, withDependencie
continue
}
if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil {
progress.ColoredPrintf("@{g}Injecting package@|: %s", p)
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
options.Progress.ColoredPrintf("@{g}Injecting package@|: %s", p)
}
result.Add(p)
dependencySource.Add(p)
_ = result.Add(p)
_ = dependencySource.Add(p)
added++
if dependencyOptions&DepFollowAllVariants == 0 {
if options.DependencyOptions&DepFollowAllVariants == 0 {
break
}
}
} else {
if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil {
progress.ColoredPrintf("@{r}Unsatisfied dependency@|: %s", dep.String())
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
options.Progress.ColoredPrintf("@{r}Unsatisfied dependency@|: %s", dep.String())
}
}
+91 -34
View File
@@ -131,7 +131,7 @@ func (s *PackageListSuite) TestAddLen(c *C) {
c.Check(s.list.Len(), Equals, 1)
c.Check(s.list.Add(s.p3), IsNil)
c.Check(s.list.Len(), Equals, 2)
c.Check(s.list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
c.Check(s.list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
}
func (s *PackageListSuite) TestRemove(c *C) {
@@ -311,7 +311,11 @@ func (s *PackageListSuite) TestSearch(c *C) {
}
func (s *PackageListSuite) TestFilter(c *C) {
c.Check(func() { s.list.Filter([]PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}}, false, nil, 0, nil) }, Panics, "list not indexed, can't filter")
c.Check(func() {
s.list.Filter(FilterOptions{
Queries: []PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}},
})
}, Panics, "list not indexed, can't filter")
plString := func(l *PackageList) string {
list := make([]string, 0, l.Len())
@@ -324,83 +328,136 @@ func (s *PackageListSuite) TestFilter(c *C) {
return strings.Join(list, " ")
}
result, err := s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}}, false, nil, 0, nil)
result, err := s.il.Filter(FilterOptions{Queries: []PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}}})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
result, err = s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, &PkgQuery{"dpkg", "1.7", "source"},
&PkgQuery{"dpkg", "1.8", "amd64"}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{Queries: []PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, &PkgQuery{"dpkg", "1.7", "source"},
&PkgQuery{"dpkg", "1.8", "amd64"}}})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_source")
result, err = s.il.Filter([]PackageQuery{
result, err = s.il.Filter(FilterOptions{Queries: []PackageQuery{
&DependencyQuery{Dep: Dependency{Pkg: "app"}},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}},
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}},
&DependencyQuery{Dep: Dependency{Pkg: "xyz"}},
&DependencyQuery{Dep: Dependency{Pkg: "aa", Relation: VersionGreater, Version: "3.0"}}}, false, nil, 0, nil)
&DependencyQuery{Dep: Dependency{Pkg: "aa", Relation: VersionGreater, Version: "3.0"}}}})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source")
result, err = s.il.Filter([]PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "app", Architecture: "i386"}}}, true, NewPackageList(), 0, []string{"i386"})
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "app", Architecture: "i386"}}},
WithDependencies: true,
Source: NewPackageList(),
Architectures: []string{"i386"},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386")
result, err = s.il.Filter([]PackageQuery{
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "0.9"}},
&DependencyQuery{Dep: Dependency{Pkg: "lib"}},
&DependencyQuery{Dep: Dependency{Pkg: "data"}}}, true, NewPackageList(), 0, []string{"i386", "amd64"})
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "0.9"}},
&DependencyQuery{Dep: Dependency{Pkg: "lib"}},
&DependencyQuery{Dep: Dependency{Pkg: "data"}}},
WithDependencies: true,
Source: NewPackageList(),
Architectures: []string{"i386", "amd64"},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386")
result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source")
result, err = s.il.Filter([]PackageQuery{&AndQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&AndQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "")
result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_i386 data_1.1~bp1_all")
result, err = s.il.Filter([]PackageQuery{&AndQuery{&FieldQuery{Field: "Version", Relation: VersionGreaterOrEqual, Value: "1.0"},
&FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&AndQuery{&FieldQuery{Field: "Version", Relation: VersionGreaterOrEqual, Value: "1.0"},
&FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 data_1.1~bp1_all")
result, err = s.il.Filter([]PackageQuery{&AndQuery{
&FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&AndQuery{
&FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}, &PkgQuery{"app", "1.1~bp1", "i386"}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
result, err = s.il.Filter([]PackageQuery{&NotQuery{
&FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&NotQuery{
&FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.6.1-3_arm dpkg_1.6.1-3_source dpkg_1.7_source libx_1.5_arm")
result, err = s.il.Filter([]PackageQuery{&AndQuery{
&FieldQuery{Field: "$Architecture", Relation: VersionRegexp, Value: "i.*6", Regexp: regexp.MustCompile("i.*6")}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&AndQuery{
&FieldQuery{Field: "$Architecture", Relation: VersionRegexp, Value: "i.*6", Regexp: regexp.MustCompile("i.*6")}, &PkgQuery{"app", "1.1~bp1", "i386"}}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
result, err = s.il.Filter([]PackageQuery{&AndQuery{
&FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")},
&NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}},
}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&AndQuery{
&FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")},
&NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}},
}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "aa_2.0-1_i386 app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 mailer_3.5.8_i386")
result, err = s.il.Filter([]PackageQuery{&AndQuery{
&NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}},
&FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")},
}}, false, nil, 0, nil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&AndQuery{
&NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}},
&FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")},
}},
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "aa_2.0-1_i386 app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 mailer_3.5.8_i386")
// Different version for the source package
for _, p := range s.sourcePackages {
c.Check(s.il.Add(p), IsNil)
}
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "lib"}}},
Architectures: []string{"i386", "amd64"},
WithSources: true,
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "lib_0.9_source lib_1.0_i386")
// Different name for the source package
err = s.il.Add(&Package{Name: "glibc", Version: "1.0", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}})
c.Check(err, IsNil)
err = s.il.Add(&Package{Name: "libc1", Version: "1.0", Architecture: "i386", Source: "glibc", deps: &PackageDependencies{}})
c.Check(err, IsNil)
result, err = s.il.Filter(FilterOptions{
Queries: []PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "libc1"}}},
Architectures: []string{"i386"},
WithSources: true,
})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "glibc_1.0_source libc1_1.0_i386")
}
func (s *PackageListSuite) TestVerifyDependencies(c *C) {
+1 -1
View File
@@ -450,7 +450,7 @@ func (p *Package) GetArchitecture() string {
return p.Architecture
}
// GetDependencies compiles list of dependncies by flags from options
// GetDependencies compiles list of dependencies by flags from options
func (p *Package) GetDependencies(options int) (dependencies []string) {
deps := p.Deps()
+5
View File
@@ -1660,6 +1660,11 @@ func (collection *PublishedRepoCollection) Remove(publishedStorageProvider aptly
return err
}
err = collection.LoadComplete(repo, collectionFactory)
if err != nil {
return err
}
removePrefix := true
removePoolComponents := repo.Components()
cleanComponents := []string{}
+9 -1
View File
@@ -596,7 +596,15 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu
emptyList.PrepareIndex()
oldLen = repo.packageList.Len()
repo.packageList, err = repo.packageList.FilterWithProgress([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures, progress)
repo.packageList, err = repo.packageList.Filter(FilterOptions{
Queries: []PackageQuery{filterQuery},
WithDependencies: repo.FilterWithDeps,
Source: emptyList,
WithSources: repo.DownloadSources,
DependencyOptions: dependencyOptions,
Architectures: repo.Architectures,
Progress: progress,
})
if repo.packageList != nil {
newLen = repo.packageList.Len()
}
-3
View File
@@ -5,7 +5,4 @@ LISTEN_ADDRESS='localhost:8080'
# aptly options:
# -no-lock allow aptly commands in parallel with api service (no global database lock, but lock per request)
APTLY_OPTIONS="-no-lock"
+343 -38
View File
@@ -1,38 +1,343 @@
{
"rootDir": "~/.aptly",
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"downloadRetries": 0,
"downloader": "default",
"databaseOpenAttempts": -1,
"architectures": [],
"dependencyFollowSuggests": false,
"dependencyFollowRecommends": false,
"dependencyFollowAllVariants": false,
"dependencyFollowSource": false,
"dependencyVerboseResolve": false,
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgProvider": "gpg",
"downloadSourcePackages": false,
"skipLegacyPool": true,
"ppaDistributorID": "ubuntu",
"ppaCodename": "",
"skipContentsPublishing": false,
"skipBz2Publishing": false,
"FileSystemPublishEndpoints": {},
"S3PublishEndpoints": {},
"SwiftPublishEndpoints": {},
"AzurePublishEndpoints": {},
"AsyncAPI": false,
"enableMetricsEndpoint": false,
"logLevel": "info",
"logFormat": "default",
"serveInAPIMode": false,
"databaseBackend": {
"type": "",
"url": "",
"dbPath": ""
},
"enableSwaggerEndpoint": false
}
# Aptly Configuration File
###########################
# vim: : filetype=yaml
# aptly 1.6.0 supports yaml configuraiton files with inline documentation and examples.
# Legacy json config files are still supported, and may be converted to yaml with `aptly config show -yaml`
# Root directory for:
# - downloaded packages (`rootDir`/pool)
# - database (`rootDir`/db)
# - published repositories (`rootDir`/public)
root_dir: ~/.aptly
# Log Level
# * debug
# * info
# * warning
# * error
log_level: info
# Log Format
# * default (text)
# * json
log_format: default
# Number of attempts to open database if it's locked by other instance
# * -1 (no retry)
database_open_attempts: -1
# Default Architectures
# empty list defaults to all available architectures
architectures:
# - amd64
# OBSOLETE
# in aptly up to version 1.0.0, package files were stored in internal package pool
# with MD5-dervied path, since 1.1.0 package pool layout was changed;
# if option is enabled, aptly stops checking for legacy paths;
# by default option is enabled for new aptly installations and disabled when
# upgrading from older versions
skip_legacy_pool: true
# Dependency following
#######################
# Follow contents of `Suggests:` field when processing dependencies for the package
dep_follow_suggests: false
# Follow contents of `Recommends:` field when processing dependencies for the package
dep_follow_recommends: false
# When dependency looks like `package-a | package-b`, follow both variants always
dep_follow_allvariants: false
# Follow dependency from binary package to source package
dep_follow_source: false
# Log additional details while resolving dependencies (useful for debugging)
dep_verbose_resolve: false
# PPA
######
# Specify paramaters for short PPA url expansion
# empty defaults to output of `lsb_release` command
ppa_distributor_id: ubuntu
# Codename for short PPA url expansion
ppa_codename: ""
# Aptly Server
###############
# Serve published repos as well as API
serve_in_api_mode: false
# Enable metrics for Prometheus client
enable_metrics_endpoint: false
# Enable API documentation on /docs
enable_swagger_endpoint: false
# OBSOLETE: use via url param ?_async=true
async_api: false
# Database
###########
# Database backend
# Type must be one of:
# * leveldb (default)
# * etcd
database_backend:
type: leveldb
# Path to leveldb files
# empty dbPath defaults to `rootDir`/db
db_path: ""
# type: etcd
# # URL to db server
# url: "127.0.0.1:2379"
# Mirroring
############
# Downloader
# * "default"
# * "grab" (more robust)
downloader: default
# Number of parallel download threads to use when downloading packages
download_concurrency: 4
# Limit in kbytes/sec on download speed while mirroring remote repositories
download_limit: 0
# Number of retries for download attempts
download_retries: 0
# Download source packages per default
download_sourcepackages: false
# Signing
##########
# GPG Provider
# * "internal" (Go internal implementation)
# * "gpg" (External `gpg` utility)
gpg_provider: gpg
# Disable signing of published repositories
gpg_disable_sign: false
# Disable signature verification of remote repositories
gpg_disable_verify: false
# Publishing
#############
# Do not publish Contents files
skip_contents_publishing: false
# Do not create bz2 files
skip_bz2_publishing: false
# Storage
##########
# Filesystem publishing endpoints
#
# aptly defaults to publish to a single publish directory under `rootDir`/public. For
# a more advanced publishing strategy, you can define one or more filesystem endpoints in the
# `FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name
# and the following associated settings.
#
# In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name`
# with `endpoint-name` as the name given in the aptly configuration file. For example:
#
# `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily`
#
filesystem_publish_endpoints:
# # Endpoint Name
# test1:
# # Directory for publishing
# root_dir: /opt/srv/aptly_public
# # File Link Method for linking files from the internal pool to the published directory
# # * hardlink
# # * symlink
# # * copy
# link_method: hardlink
# # File Copare Method for comparing existing links from the internal pool to the published directory
# # Only used when "linkMethod" is set to "copy"
# # * md5 (default: compare md5 sum)
# # * size (compare file size)
# verify_method: md5
# S3 Endpoint Support
#
# cloud storage). First, publishing
# endpoints should be described in aptly configuration file. Each endpoint has name
# and associated settings.
#
# In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
# publishing prefix on the command line, e.g.:
#
# `aptly publish snapshot wheezy-main s3:test:`
#
s3_publish_endpoints:
# # Endpoint Name
# test:
# # Amazon region for S3 bucket
# region: us-east-1
# # Bucket name
# bucket: test-bucket
# # Prefix (optional)
# # publishing under specified prefix in the bucket, defaults to
# # no prefix (bucket root)
# prefix: ""
# # Default ACLs (optional)
# # assign ACL to published files:
# # * private (default, for use with apt S3 transport)
# # * public-read (public repository)
# # * none (don't set ACL)
# acl: private
# # Credentials (optional)
# # Amazon credentials to access S3 bucket. If not supplied, environment variables
# # `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` are used
# access_key_id: ""
# secret_access_key: ""
# session_token: ""
# # Endpoint (optional)
# # When using S3-compatible cloud storage, specify hostname of service endpoint here,
# # region is ignored if endpoint is set (set region to some human-readable name)
# # (should be left blank for real Amazon S3)
# endpoint: ""
# # Storage Class (optional)
# # Amazon S3 storage class, defaults to `STANDARD`. Other values
# # available: `REDUCED_REDUNDANCY` (lower price, lower redundancy)
# storage_class: STANDARD
# # Encryption Method (optional)
# # Server-side encryption method, defaults to none. Currently
# # the only available encryption method is `AES256`
# encryption_method: none
# # Plus Workaround (optional)
# # Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by
# # creating two copies of package files with `+` in filename: one original
# # and another one with spaces instead of plus signs
# # With `plusWorkaround` enabled, package files with plus sign
# # would be stored twice. aptly might not cleanup files with spaces when published
# # repository is dropped or updated (switched) to new version of repository (snapshot)
# plus_workaround: false
# # Disable MultiDel (optional)
# # For S3-compatible cloud storages which do not support `MultiDel` S3 API,
# # enable this setting (file deletion would be slower with this setting enabled)
# disable_multidel: false
# # Force Signature v2 (optional)
# # Disable Signature V4 support, useful with non-AWS S3-compatible object stores
# # which do not support SigV4, shouldn't be enabled for AWS
# force_sigv2: false
# # Force VirtualHosted Style (optional)
# # Disable path style visit, useful with non-AWS S3-compatible object stores
# # which only support virtual hosted style
# force_virtualhosted_style: false
# # Debug (optional)
# # Enables detailed request/response dump for each S3 operation
# debug: false
# Swift Endpoint Support
#
# aptly can publish a repository directly to OpenStack Swift.
# Each endpoint has name and associated settings.
#
# In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before
# publishing prefix on the command line, e.g.:
#
# `aptly publish snapshot jessie-main swift:test:`
#
swift_publish_endpoints:
# # Endpoint Name
# test:
# # Container Name
# container: taylor1
# # Prefix (optional)
# # Publish under specified prefix in the container, defaults to no prefix (container root)
# prefix: ""
# # Credentials (optional)
# # OpenStack credentials to access Keystone. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used
# username: ""
# password: ""
# # Domain (optional)
# # OpenStack domain
# domain: ""
# domain_id: ""
# # Tenant (optional)
# # OpenStack tenant (in order to use v2 authentication)
# tenant: ""
# tenant_id: ""
# tenant_domain: ""
# tenant_domain_id: ""
# # Auth URL (optional)
# # Full url of Keystone server (including port, and version).
# # Example `http://identity.example.com:5000/v2.0`
# auth_url: ""
# Azure Endpoint Support
#
# aptly can be configured to publish repositories directly to Microsoft Azure Blob
# Storage. First, publishing endpoints should be described in the aptly
# configuration file. Each endpoint has its name and associated settings.
azure_publish_endpoints:
# # Endpoint Name
# test:
# # Container Name
# container: container1
# # Prefix (optional)
# # Publishing under specified prefix in the container, defaults to no prefix (container root)
# prefix: ""
# # Credentials
# # Azure storage account access key to access blob storage
# account_name: ""
# account_key: ""
# # Endpoint URL
# # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
# # defaults to "https://<accountName>.blob.core.windows.net"
# endpoint: ""
# Package Pool
#
# Location for storing downloaded packages
# Type must be one of:
# * local
# * azure
packagepool_storage:
# Local Pool
type: local
# Local Pool Path
# empty path defaults to `rootDir`/pool
path:
# # Azure Azure Blob Storage Pool
# type: azure
# # Container Name
# container: pool1
# # Prefix (optional)
# # Publishing under specified prefix in the container, defaults to no prefix (container root)
# prefix: ""
# # Credentials
# # Azure storage account access key to access blob storage
# account_name: ""
# account_key: ""
# # Endpoint URL
# # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
# # defaults to "https://<accountName>.blob.core.windows.net"
# endpoint: ""
+24 -2
View File
@@ -1,5 +1,27 @@
aptly (1.6.0~rc1) stable; urgency=medium
aptly (1.6.0) stable; urgency=medium
* release candidate
* fix mirroring source packages
* support yaml config per default
* provide swagger API documentation
* provide API for querying aptly storage usage
* provide snapshot pull via API
* support creating repos from snapshots
* fix mirroring flat remote repos
* support skeleton files for publishing
* use new azure sdk
* support updating the components of a published repo
* support publishing multiple distributions (-multi-dist)
* support etcd database
* allow slash (/) in distribution names
* support for storing the "local" pool on Azure
* provide copy package API
* fix publish concurrency and improve performance
* improved mirroring
* fix download throttling
* fix resuming package downloads
* fix ignoring signatures
* fix packages dependency resolution (Virtual Packages, version numbers in Provides)
* improved S3 support and performance
* fix race condition with goleveldb
-- André Roth <neolynx@gmail.com> Sat, 16 Nov 2024 12:44:06 +0100
+1
View File
@@ -77,6 +77,7 @@ Build-Depends: bash-completion,
golang-go.uber-multierr-dev,
golang-go.uber-zap-dev,
golang-etcd-server-dev (>= 3.5.15-7),
golang-gopkg-yaml.v3-dev,
git
Standards-Version: 4.7.0
Homepage: https://www.aptly.info
+5
View File
@@ -0,0 +1,5 @@
# Maintenance Operations
<div>
Manage aptlys internal metadata database and package pool.
</div>
+12
View File
@@ -0,0 +1,12 @@
# Upload Package Files
<div>
In order to add debian package files to a local repository, files are first uploaded to a temporary directory.
Then the directory (or a specific file within) is added to a repository. After adding to a repositorty, the directory resp. files are removed bt default.
All uploaded files are stored under `<rootDir>/upload/<tempdir>` directory.
For concurrent uploads from CI/CD pipelines, make sure the tempdir is unique.
</div>
+8
View File
@@ -0,0 +1,8 @@
# Manage Remote Repository Mirrors
<div>
Manage mirrors of remote Debian repositories (http, https or ftp).
Flat debian repositories, mirroring source packages and debian installers is supported.
</div>
+5
View File
@@ -0,0 +1,5 @@
# Search Package Collection
<div>
Perform operations on the whole collection of packages in apty database.
</div>
+21
View File
@@ -0,0 +1,21 @@
# Publish Repositories, Snapshots, Mirrors
<div>
Publish snapshot or local repo as Debian repository to be used as APT source on Debian based systems.
The published repository is signed with the user's GnuPG key.
Repositories can be published to local directories, Amazon S3 buckets, Azure or Swift Storage.
#### GPG Keys
GPG key is required to sign any published repository. The key pari should be generated before publishing.
Publiс part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
#### Parameters
Publish APIs use following convention to identify published repositories: `/api/publish/:prefix/:distribution`. `:distribution` is distribution name, while `:prefix` is `[<storage>:]<prefix>` (storage is optional, it defaults to empty string), if publishing prefix contains slashes `/`, they should be replaced with underscores (`_`) and underscores
should be replaced with double underscore (`__`). To specify root `:prefix`, use `:.`, as `.` is ambigious in URLs.
</div>
+9
View File
@@ -0,0 +1,9 @@
# Manage Local Repositories
<div>
A local repository is a collection of versionned packages (usually custom packages created internally).
Packages can be added, removed, moved or copied between repos.
Local repositories can be published (either directly or via snapshot) to be used a APT source on a debian based system.
</div>
+8
View File
@@ -0,0 +1,8 @@
# Manage Snapshots
<div>
Local Repositories and Mirrors can be snapshotted to get an immutable state.
Snapshots cab be merged, filtered, verified for missing dependencies. Snapshots can be published to be used as APT source.
</div>
+5
View File
@@ -0,0 +1,5 @@
# Status Information
<div>
Various status information.
</div>
+8
View File
@@ -0,0 +1,8 @@
# Background Tasks
<div>
Several API operations allow to be run in background asynchronously in a task. In that case, a Task object with an ID and a State is returned, which can be queried for progress.
Tasks should be deleted once they are no longer in progress, in order to not cause memory overflows.
</div>
+9
View File
@@ -0,0 +1,9 @@
Aptly operations are also available via REST API served with `aptly api serve`.
On Debian based systems, a package `aptly-api` is available, which will run aptly as systemd service as dedicated aptly-api user.
Some configuration changes (S3 publishing endpoints, ...) will require restarting the aptly service in order to take effect.
The REST API shouldn't be exposed to the Internet as there is no authentication/protection, consider using a HTTP proxy (e.g. nginx) to add https and authentication.
#### Aptly REST API Documentation
+175
View File
@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="/docs/swagger-ui.css" >
<link rel="icon" type="image/png" href="/docs/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/docs/favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
/* no json link */
.swagger-ui a.link {
display: none;
}
/* spacing */
.swagger-ui .info {
margin: 0px !important;
}
/* API Summary */
.swagger-ui .opblock-summary-description {
flex: 0 0 auto !important;
margin-left: auto !important;
margin-right: 2em;
font-weight: bold;
font-size: 16pt !important;
}
/* Group Spacing */
.swagger-ui .opblock-tag-section {
padding-bottom: 40px !important;
}
/* Tag Group */
.swagger-ui .opblock-tag-section > .opblock-tag > a {
color: grey !important;
align-self: flex-start;
font-size: 14pt;
width: 7em;
}
/* Group Heading */
.swagger-ui .opblock-tag-section .opblock-tag .markdown > h1 {
font-size: 14pt;
margin-top: 0px;
}
/* Group Description */
.swagger-ui .opblock-tag-section .opblock-tag .markdown > div {
font-size: 12pt;
}
/* Show Tag Group description only if opened */
.swagger-ui .opblock-tag-section:not(.is-open) .opblock-tag .markdown > div {
display: none;
}
/* Keep open button on top */
.swagger-ui .expand-operation {
align-self: flex-start;
}
#swagger-ui {
padding-bottom: 112px;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="/docs/swagger-ui-bundle.js"> </script>
<script src="/docs/swagger-ui-standalone-preset.js"> </script>
<script>
function collapseAll() {
blocks = document.getElementsByClassName("opblock-tag");
for (let i = 0; i < blocks.length; i++) {
blocks[i].click();
}
models = document.getElementsByClassName("models-control");
for (let i = 0; i < models.length; i++) {
models[i].click();
}
}
window.onload = function() {
const ui = SwaggerUIBundle({
url: "\/docs\/doc.json",
dom_id: '#swagger-ui',
validatorUrl: null,
oauth2RedirectUrl: `${window.location.protocol}//${window.location.host}${window.location.pathname.split('/').slice(0, window.location.pathname.split('/').length - 1).join('/')}/oauth2-redirect.html`,
persistAuthorization: false ,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset.slice(1) // remote topbar
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
docExpansion: "list",
deepLinking: true ,
defaultModelsExpandDepth: 5
})
const defaultClientId = "";
if (defaultClientId) {
ui.initOAuth({
clientId: defaultClientId
})
}
window.ui = ui
setTimeout(collapseAll, 50);
}
</script>
</body>
</html>
+8 -1
View File
@@ -1,3 +1,10 @@
package docs
import _ "github.com/swaggo/swag" // make sure swag is in go.mod
import (
_ "embed" // embed html below
_ "github.com/swaggo/swag" // make sure swag is in go.mod
)
//go:embed docs.html
var DocsHTML []byte
+28
View File
@@ -0,0 +1,28 @@
package docs
// @title Aptly API
// @description.markdown
// @contact.name Aptly
// @contact.url http://github.com/aptly-dev/aptly
// @Tag.name Repos
// @Tag.description.markdown
// @Tag.name Files
// @Tag.description.markdown
// @Tag.name Mirrors
// @Tag.description.markdown
// @Tag.name Snapshots
// @Tag.description.markdown
// @Tag.name Publish
// @Tag.description.markdown
// @Tag.name Packages
// @Tag.description.markdown
// @Tag.name Status
// @Tag.description.markdown
// @Tag.name Database
// @Tag.description.markdown
// @Tag.name Tasks
// @Tag.description.markdown
// version will be appended here:
+3 -3
View File
@@ -120,7 +120,7 @@ func (s *PackagePoolSuite) TestImportOk(c *C) {
if isSameDevice(s) {
c.Check(info.Sys().(*syscall.Stat_t).Nlink > 1, Equals, true)
} else {
c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint64(1))
c.Check(info.Sys().(*syscall.Stat_t).Nlink == 1, Equals, true)
}
// import as different name
@@ -359,7 +359,7 @@ func (s *PackagePoolSuite) TestLink(c *C) {
if isSameDevice(s) {
c.Check(info.Sys().(*syscall.Stat_t).Nlink > 2, Equals, true)
} else {
c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint64(2))
c.Check(info.Sys().(*syscall.Stat_t).Nlink == 2, Equals, true)
}
}
@@ -377,7 +377,7 @@ func (s *PackagePoolSuite) TestSymlink(c *C) {
if isSameDevice(s) {
c.Check(info.Sys().(*syscall.Stat_t).Nlink > 2, Equals, true)
} else {
c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint64(1))
c.Check(info.Sys().(*syscall.Stat_t).Nlink == 1, Equals, true)
}
info, err = os.Lstat(dstPath)
+19 -19
View File
@@ -46,19 +46,19 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.17.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -113,20 +113,20 @@ require (
google.golang.org/grpc v1.64.1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/ProtonMail/go-crypto v1.0.0
github.com/aws/aws-sdk-go-v2 v1.30.3
github.com/aws/aws-sdk-go-v2/config v1.25.1
github.com/aws/aws-sdk-go-v2/credentials v1.16.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3
github.com/aws/smithy-go v1.20.3
github.com/aws/aws-sdk-go-v2 v1.32.5
github.com/aws/aws-sdk-go-v2/config v1.28.5
github.com/aws/aws-sdk-go-v2/credentials v1.17.46
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1
github.com/aws/smithy-go v1.22.1
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
go.etcd.io/etcd/client/v3 v3.5.15
gopkg.in/yaml.v3 v3.0.1
)
+36 -36
View File
@@ -24,42 +24,42 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/awalterschulze/gographviz v2.0.1+incompatible h1:XIECBRq9VPEQqkQL5pw2OtjCAdrtIgFKoJU8eT98AS8=
github.com/awalterschulze/gographviz v2.0.1+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/config v1.25.1 h1:YsjngBOl2mx4l3egkVWndr6/6TqtkdsWJFZIsQ924Ek=
github.com/aws/aws-sdk-go-v2/config v1.25.1/go.mod h1:yV6h7TRVzhdIFmUk9WWDRpWwYGg1woEzKr0k1IYz2Tk=
github.com/aws/aws-sdk-go-v2/credentials v1.16.1 h1:WessyrdgyFN5TB+eLQdrFSlN/3oMnqukIFhDxK6z8h0=
github.com/aws/aws-sdk-go-v2/credentials v1.16.1/go.mod h1:RQJyPxKcr+m4ArlIG1LUhMOrjposVfzbX6H8oR6oCgE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4 h1:9wKDWEjwSnXZre0/O3+ZwbBl1SmlgWYBbrTV10X/H1s=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4/go.mod h1:t4i+yGHMCcUNIX1x7YVYa6bH/Do7civ5I6cG/6PMfyA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.0 h1:usgqiJtamuGIBj+OvYmMq89+Z1hIKkMJToz1WpoeNUY=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.0/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.2 h1:V47N5eKgVZoRSvx2+RQ0EpAEit/pqOhqeSQFiS4OFEQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.2/go.mod h1:/pE21vno3q1h4bbhUOEi+6Zu/aT26UK2WKkDXd+TssQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.2 h1:sMAcO7VHVw28HTAdZpTULDzFirHOsVm/x25CxhUH0jA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.2/go.mod h1:dWqm5G767qwKPuayKfzm4rjzFmVjiBFbOJrpSPnAMDs=
github.com/aws/aws-sdk-go-v2/service/sts v1.25.2 h1:vwyiRTnXLqsak/6WAQ+uTRhVqKI6vxUQ0HJXjKij0zM=
github.com/aws/aws-sdk-go-v2/service/sts v1.25.2/go.mod h1:4EqRHDCKP78hq3zOnmFXu5k0j4bXbRFfCh/zQ6KnEfQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo=
github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w=
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c=
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg=
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+4
View File
@@ -13,12 +13,16 @@ import (
//go:embed VERSION
var Version string
//go:embed debian/aptly.conf
var AptlyConf []byte
func main() {
if Version == "" {
Version = "unknown"
}
aptly.Version = Version
aptly.AptlyConf = AptlyConf
os.Exit(cmd.Run(cmd.RootCommand(), os.Args[1:], true))
}
+1
View File
@@ -51,6 +51,7 @@ func TestRunMain(t *testing.T) {
}
aptly.Version = Version
aptly.AptlyConf = AptlyConf
args := filterOutTestArgs(os.Args[1:])
root := cmd.RootCommand()
+483 -316
View File
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "APTLY" "1" "October 2024" "" ""
.TH "APTLY" "1" "December 2024" "" ""
.
.SH "NAME"
\fBaptly\fR \- Debian repository management tool
@@ -28,330 +28,390 @@ aptly\(cqs goal is to establish repeatability and controlled changes in a packag
aptly looks for configuration file first in \fB~/\.aptly\.conf\fR then in \fB/usr/local/etc/aptly\.conf\fR and \fB/etc/aptly\.conf\fR\. If no config file found (or they are not readable), a new one is created in the home directory\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly/\fR, it will be created if missing\.
.
.P
Configuration file is stored in JSON format (default values shown below):
With aptly version 1\.6\.0, yaml configuration with inline documentation is supported and recommended (see \fBdebian/aptly\.conf\fR)\.
.
.P
The legacy json configuration is still supported:
.
.IP "" 4
.
.nf
// vim: : filetype=json
// json configuration file with comments
// validate with: sed \(cq/\e/\e//d\(cq aptly\.conf | json_pp
{
"rootDir": "$HOME/\.aptly",
"databaseBackend": {
"type": "",
"url": ""
},
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"downloadRetries": 0,
"downloader": "default",
"databaseOpenAttempts": 10,
// Aptly Configuration File
////////////////////////////
// Root directory for:
// \- downloaded packages (`rootDir`/pool)
// \- database (`rootDir`/db)
// \- published repositories (`rootDir`/public)
"rootDir": "~/\.aptly",
// Number of attempts to open database if it\(cqs locked by other instance
// * \-1 (no retry)
"databaseOpenAttempts": \-1,
// Log Level
// * debug
// * info
// * warning
// * error
"logLevel": "info",
// Log Format
// * default (text)
// * json
"logFormat": "default",
// Default Architectures
// empty array defaults to all available architectures
"architectures": [],
// Follow contents of `Suggests:` field when processing dependencies for the package
"dependencyFollowSuggests": false,
// Follow contents of `Recommends:` field when processing dependencies for the package
"dependencyFollowRecommends": false,
// When dependency looks like `package\-a | package\-b`, follow both variants always
"dependencyFollowAllVariants": false,
// Follow dependency from binary package to source package
"dependencyFollowSource": false,
// Log additional details while resolving dependencies (useful for debugging)
"dependencyVerboseResolve": false,
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgProvider": "gpg",
"downloadSourcePackages": false,
"packagePoolStorage": {
"path": "$ROOTDIR/pool",
"azure": {
"accountName": "",
"accountKey": "",
"container": "repo",
"prefix": "",
"endpoint": ""
}
},
"skipLegacyPool": true,
// Specifies paramaters for short PPA url expansion
// empty defaults to output of `lsb_release` command
"ppaDistributorID": "ubuntu",
// Codename for short PPA url expansion
"ppaCodename": "",
// OBSOLETE
// in aptly up to version 1\.0\.0, package files were stored in internal package pool
// with MD5\-dervied path, since 1\.1\.0 package pool layout was changed;
// if option is enabled, aptly stops checking for legacy paths;
// by default option is enabled for new aptly installations and disabled when
// upgrading from older versions
"skipLegacyPool": true,
// Aptly Server
////////////////
// Serve published repos as well as API
"serveInAPIMode": false,
// Enable metrics for Prometheus client
"enableMetricsEndpoint": false,
// Enable API documentation on /docs
"enableSwaggerEndpoint": false,
// OBSOLETE: use via url param ?_async=true
"AsyncAPI": false,
// Database
////////////
// Database backend
// Type must be one of:
// * leveldb (default)
// * etcd
"databaseBackend": {
// LevelDB
"type": "leveldb",
// Path to leveldb files
// empty dbPath defaults to `rootDir`/db
"dbPath": ""
// // etcd
// "type": "etcd",
// // URL to db server
// "url": "127\.0\.0\.1:2379"
},
// Mirroring
/////////////
// Downloader
// * "default"
// * "grab" (more robust)
"downloader": "default",
// Number of parallel download threads to use when downloading packages
"downloadConcurrency": 4,
// Limit in kbytes/sec on download speed while mirroring remote repositories
"downloadSpeedLimit": 0,
// Number of retries for download attempts
"downloadRetries": 0,
// Download source packages per default
"downloadSourcePackages": false,
// Signing
///////////
// GPG Provider
// * "internal" (Go internal implementation)
// * "gpg" (External `gpg` utility)
"gpgProvider": "gpg",
// Disable signing of published repositories
"gpgDisableSign": false,
// Disable signature verification of remote repositories
"gpgDisableVerify": false,
// Publishing
//////////////
// Do not publish Contents files
"skipContentsPublishing": false,
// Do not create bz2 files
"skipBz2Publishing": false,
// Storage
///////////
// Filesystem publishing endpoints
//
// aptly defaults to publish to a single publish directory under `rootDir`/public\. For
// a more advanced publishing strategy, you can define one or more filesystem endpoints in the
// `FileSystemPublishEndpoints` list of the aptly configuration file\. Each endpoint has a name
// and the following associated settings\.
//
// In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint\-name`
// with `endpoint\-name` as the name given in the aptly configuration file\. For example:
//
// `aptly publish snapshot wheezy\-main filesystem:test1:wheezy/daily`
//
"FileSystemPublishEndpoints": {
"test1": {
"rootDir": "/opt/srv1/aptly_public",
"linkMethod": "symlink"
},
"test2": {
"rootDir": "/opt/srv2/aptly_public",
"linkMethod": "copy",
"verifyMethod": "md5"
},
"test3": {
"rootDir": "/opt/srv3/aptly_public",
"linkMethod": "hardlink"
}
// // Endpoint Name
// "test1": {
// // Directory for publishing
// "rootDir": "/opt/srv/aptly_public",
// // File Link Method for linking files from the internal pool to the published directory
// // * hardlink
// // * symlink
// // * copy
// "linkMethod": "hardlink",
// // File Copare Method for comparing existing links from the internal pool to the published directory
// // Only used when "linkMethod" is set to "copy"
// // * md5 (default: compare md5 sum)
// // * size (compare file size)
// "verifyMethod": "md5"
// }
},
// S3 Endpoint Support
//
// cloud storage)\. First, publishing
// endpoints should be described in aptly configuration file\. Each endpoint has name
// and associated settings\.
//
// In order to publish to S3, specify endpoint as `s3:endpoint\-name:` before
// publishing prefix on the command line, e\.g\.:
//
// `aptly publish snapshot wheezy\-main s3:test:`
//
"S3PublishEndpoints": {
"test": {
"region": "us\-east\-1",
"bucket": "repo",
"endpoint": "",
"awsAccessKeyID": "",
"awsSecretAccessKey": "",
"prefix": "",
"acl": "public\-read",
"storageClass": "",
"encryptionMethod": "",
"plusWorkaround": false,
"disableMultiDel": false,
"forceSigV2": false,
"forceVirtualHostedStyle": true,
"debug": false
}
// // Endpoint Name
// "test": {
// // Amazon region for S3 bucket
// "region": "us\-east\-1",
// // Bucket name
// "bucket": "test\-bucket",
// // Endpoint (optional)
// // When using S3\-compatible cloud storage, specify hostname of service endpoint here,
// // region is ignored if endpoint is set (set region to some human\-readable name)
// // (should be left blank for real Amazon S3)
// "endpoint": "",
// // Prefix (optional)
// // publishing under specified prefix in the bucket, defaults to
// // no prefix (bucket root)
// "prefix": "",
// // Default ACLs (optional)
// // assign ACL to published files (one of the canned ACLs in Amazon
// // terminology)\. Useful values: `private` (default), `public\-read` (public
// // repository) or `none` (don\(cqt set ACL)\. Public repositories could be consumed by `apt` using
// // HTTP endpoint (Amazon bucket should be configured for "website hosting"),
// // for private repositories special apt S3 transport is required\.
// "acl": "private",
// // Credentials (optional)
// // Amazon credentials to access S3 bucket\. If not supplied,
// // environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
// // are used\.
// "awsAccessKeyID": "",
// "awsSecretAccessKey": "",
// // Storage Class (optional)
// // Amazon S3 storage class, defaults to `STANDARD`\. Other values
// // available: `REDUCED_REDUNDANCY` (lower price, lower redundancy)
// "storageClass": "STANDARD",
// // Encryption Method (optional)
// // Server\-side encryption method, defaults to none\. Currently
// // the only available encryption method is `AES256`
// "encryptionMethod": "none",
// // Plus Workaround (optional)
// // Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by
// // creating two copies of package files with `+` in filename: one original
// // and another one with spaces instead of plus signs
// // With `plusWorkaround` enabled, package files with plus sign
// // would be stored twice\. aptly might not cleanup files with spaces when published
// // repository is dropped or updated (switched) to new version of repository (snapshot)
// "plusWorkaround": false,
// // Disable MultiDel (optional)
// // For S3\-compatible cloud storages which do not support `MultiDel` S3 API,
// // enable this setting (file deletion would be slower with this setting enabled)
// "disableMultiDel": false,
// // ForceSig2 (optional)
// // Disable Signature V4 support, useful with non\-AWS S3\-compatible object stores
// // which do not support SigV4, shouldn\(cqt be enabled for AWS
// "forceSigV2": false,
// // ForceVirtualHostedStyle (optional)
// // Disable path style visit, useful with non\-AWS S3\-compatible object stores
// // which only support virtual hosted style
// "forceVirtualHostedStyle": false,
// // Debug (optional)
// // Enables detailed request/response dump for each S3 operation
// "debug": false
// }
},
// Swift Endpoint Support
//
// aptly could be configured to publish repository directly to OpenStack Swift\. First,
// publishing endpoints should be described in aptly configuration file\. Each endpoint
// has name and associated settings\.
//
// In order to publish to Swift, specify endpoint as `swift:endpoint\-name:` before
// publishing prefix on the command line, e\.g\.:
//
// `aptly publish snapshot jessie\-main swift:test:`
//
"SwiftPublishEndpoints": {
"test": {
"container": "repo",
"osname": "",
"password": "",
"prefix": "",
"authurl": "",
"tenant": "",
"tenantid": ""
}
// Endpoint Name
// "test": {
// // Container Name
// "container": "taylor1",
// // Prefix (optional)
// // Publish under specified prefix in the container, defaults to no prefix (container root)
// "prefix": "",
// // Credentials (optional)
// // OpenStack credentials to access Keystone\. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used
// "osname": "",
// "password": "",
// // Tenant (optional)
// // OpenStack tenant name and id (in order to use v2 authentication)
// "tenant": "",
// "tenantid": "",
// // Auth URL (optional)
// // Full url of Keystone server (including port, and version)\.
// // Example `http://identity\.example\.com:5000/v2\.0`
// "authurl": ""
// }
},
// Azure Endpoint Support
//
// aptly can be configured to publish repositories directly to Microsoft Azure Blob
// Storage\. First, publishing endpoints should be described in the aptly
// configuration file\. Each endpoint has its name and associated settings\.
"AzurePublishEndpoints": {
"test": {
"accountName": "",
"accountKey": "",
"container": "repo",
"prefix": "",
"endpoint": "blob\.core\.windows\.net"
}
// // Endpoint Name
// "test": {
// // Container Name
// "container": "container1",
// // Prefix (optional)
// // Publishing under specified prefix in the container, defaults to no prefix (container root)
// "prefix": "",
// // Credentials
// // Azure storage account access key to access blob storage
// "accountName": "",
// "accountKey": "",
// // Endpoint URL
// // See: Azure documentation https://docs\.microsoft\.com/en\-us/azure/storage/common/storage\-configure\-connection\-string
// // defaults to "https://<accountName>\.blob\.core\.windows\.net"
// "endpoint": ""
// }
},
// Package Pool
// Location for storing downloaded packages
// Type must be one of:
// * local
// * azure
"packagePoolStorage": {
// Local Pool
"type": "local",
// Local Pool Path
// empty path defaults to `rootDir`/pool
"path": ""
// // Azure Azure Blob Storage Pool
// "type": "azure",
// "container": "pool1",
// // Prefix (optional)
// // Publishing under specified prefix in the container, defaults to no prefix (container root)
// "prefix": "",
// // Credentials
// // Azure storage account access key to access blob storage
// "accountName": "",
// "accountKey": "",
// // Endpoint URL
// // See: Azure documentation https://docs\.microsoft\.com/en\-us/azure/storage/common/storage\-configure\-connection\-string
// // defaults to "https://<accountName>\.blob\.core\.windows\.net"
// "endpoint": ""
}
// End of config
}
.
.fi
.
.IP "" 0
.
.P
Options:
.
.IP "\[ci]" 4
\fBrootDir\fR: is root of directory storage to store database (\fBrootDir\fR/db), the default for downloaded packages (\fBrootDir\fR/pool) and the default for published repositories (\fBrootDir\fR/public)
.
.IP "\[ci]" 4
\fBdatabaseBackend\fR: the database config; if this config is empty, use levledb backend by default
.
.IP "\[ci]" 4
\fBdownloadConcurrency\fR: is a number of parallel download threads to use when downloading packages
.
.IP "\[ci]" 4
\fBdownloadSpeedLimit\fR: limit in kbytes/sec on download speed while mirroring remote repositories
.
.IP "\[ci]" 4
\fBdownloadRetries\fR: number of retries for download attempts
.
.IP "\[ci]" 4
\fBdatabaseOpenAttempts\fR: number of attempts to open DB if it\(cqs locked by other instance; could be overridden with option \fB\-db\-open\-attempts\fR
.
.IP "\[ci]" 4
\fBarchitectures\fR: is a list of architectures to process; if left empty defaults to all available architectures; could be overridden with option \fB\-architectures\fR
.
.IP "\[ci]" 4
\fBdependencyFollowSuggests\fR: follow contents of \fBSuggests:\fR field when processing dependencies for the package
.
.IP "\[ci]" 4
\fBdependencyFollowRecommends\fR: follow contents of \fBRecommends:\fR field when processing dependencies for the package
.
.IP "\[ci]" 4
\fBdependencyFollowAllVariants\fR: when dependency looks like \fBpackage\-a | package\-b\fR, follow both variants always
.
.IP "\[ci]" 4
\fBdependencyFollowSource\fR: follow dependency from binary package to source package
.
.IP "\[ci]" 4
\fBdependencyVerboseResolve\fR: print additional details while resolving dependencies (useful for debugging)
.
.IP "\[ci]" 4
\fBgpgDisableSign\fR: don\(cqt sign published repositories with gpg(1), also can be disabled on per\-repo basis using \fB\-skip\-signing\fR flag when publishing
.
.IP "\[ci]" 4
\fBgpgDisableVerify\fR: don\(cqt verify remote mirrors with gpg(1), also can be disabled on per\-mirror basis using \fB\-ignore\-signatures\fR flag when creating and updating mirrors
.
.IP "\[ci]" 4
\fBgpgProvider\fR: implementation of PGP signing/validation \- \fBgpg\fR for external \fBgpg\fR utility or \fBinternal\fR to use Go internal implementation; \fBgpg1\fR might be used to force use of GnuPG 1\.x, \fBgpg2\fR enables GnuPG 2\.x only; default is to use GnuPG 1\.x if available and GnuPG 2\.x otherwise
.
.IP "\[ci]" 4
\fBdownloadSourcePackages\fR: if enabled, all mirrors created would have flag set to download source packages; this setting could be controlled on per\-mirror basis with \fB\-with\-sources\fR flag
.
.IP "\[ci]" 4
\fBpackagePoolStorage\fR: configures the location to store downloaded packages (defaults to the path \fB$ROOTDIR/pool\fR), by setting the value of the \fBtype\fR:
.
.IP "\[ci]" 4
\fBpath\fR: store the packages in the given path
.
.IP "\[ci]" 4
\fBazure\fR: store the packages in the given Azure Blob Storage container (see the section on Azure publishing below for information on the configuration)
.
.IP "" 0
.
.IP "\[ci]" 4
\fBskipLegacyPool\fR: in aptly up to version 1\.0\.0, package files were stored in internal package pool with MD5\-dervied path, since 1\.1\.0 package pool layout was changed; if option is enabled, aptly stops checking for legacy paths; by default option is enabled for new aptly installations and disabled when upgrading from older versions
.
.IP "\[ci]" 4
\fBppaDistributorID\fR, \fBppaCodename\fR: specifies paramaters for short PPA url expansion, if left blank they default to output of \fBlsb_release\fR command
.
.IP "\[ci]" 4
\fBFileSystemPublishEndpoints\fR: configuration of local filesystem publishing endpoints (see below)
.
.IP "\[ci]" 4
\fBS3PublishEndpoints\fR: configuration of Amazon S3 publishing endpoints (see below)
.
.IP "\[ci]" 4
\fBSwiftPublishEndpoints\fR: configuration of OpenStack Swift publishing endpoints (see below)
.
.IP "\[ci]" 4
\fBAzurePublishEndpoints\fR: configuration of Azure publishing endpoints (see below)
.
.IP "" 0
.
.SH "CUSTOM PACKAGE POOLS"
aptly defaults to storing downloaded packages at \fBrootDir/\fRpool\. In order to change this, you can set the \fBtype\fR key within \fBpackagePoolStorage\fR to one of two values:
.
.IP "\[ci]" 4
\fBlocal\fR: Store the package pool locally (the default)\. In order to change the path, additionally set the \fBpath\fR key within \fBpackagePoolStorage\fR to the desired location\.
.
.IP "\[ci]" 4
\fBazure\fR: Store the package pool in an Azure Blob Storage container\. Any keys in the below section on Azure publishing may be set on the \fBpackagePoolStorage\fR object in order to configure the Azure connection\.
.
.IP "" 0
.
.SH "FILESYSTEM PUBLISHING ENDPOINTS"
aptly defaults to publish to a single publish directory under \fBrootDir\fR/public\. For a more advanced publishing strategy, you can define one or more filesystem endpoints in the \fBFileSystemPublishEndpoints\fR list of the aptly configuration file\. Each endpoint has a name and the following associated settings:
.
.TP
\fBrootDir\fR
The publish directory, e\.g\., \fB/opt/srv/aptly_public\fR\.
.
.TP
\fBlinkMethod\fR
This is one of \fBhardlink\fR, \fBsymlink\fR or \fBcopy\fR\. It specifies how aptly links the files from the internal pool to the published directory\. If not specified, empty or wrong, this defaults to \fBhardlink\fR\.
.
.TP
\fBverifyMethod\fR
This is used only when setting the \fBlinkMethod\fR to \fBcopy\fR\. Possible values are \fBmd5\fR and \fBsize\fR\. It specifies how aptly compares existing links from the internal pool to the published directory\. The \fBsize\fR method compares only the file sizes, whereas the \fBmd5\fR method calculates the md5 checksum of the found file and compares it to the desired one\. If not specified, empty or wrong, this defaults to \fBmd5\fR\.
.
.P
In order to publish to such an endpoint, specify the endpoint as \fBfilesystem:endpoint\-name\fR with \fBendpoint\-name\fR as the name given in the aptly configuration file\. For example:
.
.P
\fBaptly publish snapshot wheezy\-main filesystem:test1:wheezy/daily\fR
.
.SH "S3 PUBLISHING ENDPOINTS"
aptly could be configured to publish repository directly to Amazon S3 (or S3\-compatible cloud storage)\. First, publishing endpoints should be described in aptly configuration file\. Each endpoint has name and associated settings:
.
.TP
\fBregion\fR
Amazon region for S3 bucket (e\.g\. \fBus\-east\-1\fR)
.
.TP
\fBbucket\fR
bucket name
.
.TP
\fBendpoint\fR
(optional) when using S3\-compatible cloud storage, specify hostname of service endpoint here, region is ignored if endpoint is set (set region to some human\-readable name) (should be left blank for real Amazon S3)
.
.TP
\fBprefix\fR
(optional) do publishing under specified prefix in the bucket, defaults to no prefix (bucket root)
.
.TP
\fBacl\fR
(optional) assign ACL to published files (one of the canned ACLs in Amazon terminology)\. Useful values: \fBprivate\fR (default), \fBpublic\-read\fR (public repository) or \fBnone\fR (don\(cqt set ACL)\. Public repositories could be consumed by \fBapt\fR using HTTP endpoint (Amazon bucket should be configured for "website hosting"), for private repositories special apt S3 transport is required\.
.
.TP
\fBawsAccessKeyID\fR, \fBawsSecretAccessKey\fR
(optional) Amazon credentials to access S3 bucket\. If not supplied, environment variables \fBAWS_ACCESS_KEY_ID\fR and \fBAWS_SECRET_ACCESS_KEY\fR are used\.
.
.TP
\fBstorageClass\fR
(optional) Amazon S3 storage class, defaults to \fBSTANDARD\fR\. Other values available: \fBREDUCED_REDUNDANCY\fR (lower price, lower redundancy)
.
.TP
\fBencryptionMethod\fR
(optional) server\-side encryption method, defaults to none\. Currently the only available encryption method is \fBAES256\fR
.
.TP
\fBplusWorkaround\fR
(optional) workaround misbehavior in apt and Amazon S3 for files with \fB+\fR in filename by creating two copies of package files with \fB+\fR in filename: one original and another one with spaces instead of plus signs With \fBplusWorkaround\fR enabled, package files with plus sign would be stored twice\. aptly might not cleanup files with spaces when published repository is dropped or updated (switched) to new version of repository (snapshot)
.
.TP
\fBdisableMultiDel\fR
(optional) for S3\-compatible cloud storages which do not support \fBMultiDel\fR S3 API, enable this setting (file deletion would be slower with this setting enabled)
.
.TP
\fBforceSigV2\fR
(optional) disable Signature V4 support, useful with non\-AWS S3\-compatible object stores which do not support SigV4, shouldn\(cqt be enabled for AWS
.
.TP
\fBforceVirtualHostedStyle\fR
(optional) disable path style visit, useful with non\-AWS S3\-compatible object stores which only support virtual hosted style
.
.TP
\fBdebug\fR
(optional) enables detailed request/response dump for each S3 operation
.
.P
In order to publish to S3, specify endpoint as \fBs3:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.:
.
.P
\fBaptly publish snapshot wheezy\-main s3:test:\fR
.
.SH "OPENSTACK SWIFT PUBLISHING ENDPOINTS"
aptly could be configured to publish repository directly to OpenStack Swift\. First, publishing endpoints should be described in aptly configuration file\. Each endpoint has name and associated settings:
.
.TP
\fBcontainer\fR
container name
.
.TP
\fBprefix\fR
(optional) do publishing under specified prefix in the container, defaults to no prefix (container root)
.
.TP
\fBosname\fR, \fBpassword\fR
(optional) OpenStack credentials to access Keystone\. If not supplied, environment variables \fBOS_USERNAME\fR and \fBOS_PASSWORD\fR are used\.
.
.TP
\fBtenant\fR, \fBtenantid\fR
(optional) OpenStack tenant name and id (in order to use v2 authentication)\.
.
.TP
\fBauthurl\fR
(optional) the full url of Keystone server (including port, and version)\. example \fBhttp://identity\.example\.com:5000/v2\.0\fR
.
.P
In order to publish to Swift, specify endpoint as \fBswift:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.:
.
.P
\fBaptly publish snapshot jessie\-main swift:test:\fR
.
.SH "AZURE PUBLISHING ENDPOINTS"
aptly can be configured to publish repositories directly to Microsoft Azure Blob Storage\. First, publishing endpoints should be described in the aptly configuration file\. Each endpoint has its name and associated settings:
.
.TP
\fBcontainer\fR
container name
.
.TP
\fBprefix\fR
(optional) do publishing under specified prefix in the container, defaults to no prefix (container root)
.
.TP
\fBaccountName\fR, \fBaccountKey\fR
Azure storage account access key to access blob storage
.
.TP
\fBendpoint\fR
endpoint URL to connect to, as described in the Azure documentation \fIhttps://docs\.microsoft\.com/en\-us/azure/storage/common/storage\-configure\-connection\-string\fR; defaults to \fBhttps://$accountName\.blob\.core\.windows\.net\fR
.
.SH "PACKAGE QUERY"
Some commands accept package queries to identify list of packages to process\. Package query syntax almost matches \fBreprepro\fR query language\. Query consists of the following simple terms:
.
@@ -771,7 +831,7 @@ custom format for result printing
include dependencies into search results
.
.SH "ADD PACKAGES TO LOCAL REPOSITORY"
\fBaptly\fR \fBrepo\fR \fBadd\fR \fIname\fR
\fBaptly\fR \fBrepo\fR \fBadd\fR \fIname\fR \fB(<package\fR \fBfile\.deb>|<directory>)\|\.\|\.\|\.\fR
.
.P
Command adds packages to local repository from \.deb, \.udeb (binary packages) and \.dsc (source packages) files\. When importing from directory aptly would do recursive scan looking for all files matching \fI\.[u]deb or\fR\.dsc patterns\. Every file discovered would be analyzed to extract metadata, package would then be created and added to the database\. Files would be imported to internal package pool\. For source packages, all required files are added automatically as well\. Extra files for source package should be in the same directory as *\.dsc file\.
@@ -1057,7 +1117,7 @@ custom format for result printing
include dependencies into search results
.
.SH "ADD PACKAGES TO LOCAL REPOSITORIES BASED ON \.CHANGES FILES"
\fBaptly\fR \fBrepo\fR \fBinclude\fR
\fBaptly\fR \fBrepo\fR \fBinclude\fR \fB(<file\.changes>|<directory>)\|\.\|\.\|\.\fR
.
.P
Command include looks for \.changes files in list of arguments or specified directories\. Each \.changes file is verified, parsed, referenced files are put into separate temporary directory and added into local repository\. Successfully imported files are removed by default\.
@@ -1103,7 +1163,7 @@ which repo should files go to, defaults to Distribution field of \.changes file
path to uploaders\.json file
.
.SH "CREATES SNAPSHOT OF MIRROR (LOCAL REPOSITORY) CONTENTS"
\fBaptly\fR \fBsnapshot\fR \fBcreate\fR \fIname\fR \fBfrom\fR \fBmirror\fR \fImirror\-name\fR \fB|\fR \fBfrom\fR \fBrepo\fR \fIrepo\-name\fR \fB|\fR \fBempty\fR
\fBaptly\fR \fBsnapshot\fR \fBcreate\fR \fIname\fR \fB(from\fR \fBmirror\fR \fImirror\-name\fR \fB|\fR \fBfrom\fR \fBrepo\fR \fIrepo\-name\fR \fB|\fR \fBempty)\fR
.
.P
Command create \fIname\fR from mirror makes persistent immutable snapshot of remote repository mirror\. Snapshot could be published or further modified using merge, pull and other aptly features\.
@@ -1706,11 +1766,14 @@ don\(cqt sign Release files with GPG
\-\fBsuite\fR=
suite to publish (defaults to distribution)
.
.SH "ADD SOURCE TO STAGED SOURCE LIST OF PUBLISHED REPOSITORY"
.SH "ADD SOURCE COMPONENTS TO A PUBLISHED REPO"
\fBaptly\fR \fBpublish\fR \fBsource\fR \fBadd\fR \fIdistribution\fR \fIsource\fR
.
.P
The command adds sources to the staged source list of the published repository\.
The command adds components of a snapshot or local repository to be published\.
.
.P
This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\.
.
.P
The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be modified\. The number of given components must be equal to the number of given sources, e\.g\.:
@@ -1719,7 +1782,7 @@ The flag \-component is mandatory\. Use a comma\-separated list of components, i
.
.nf
aptly publish add \-component=main,contrib wheezy wheezy\-main wheezy\-contrib
aptly publish source add \-component=main,contrib wheezy wheezy\-main wheezy\-contrib
.
.fi
.
@@ -1732,7 +1795,7 @@ Example:
.
.nf
$ aptly publish add \-component=contrib wheezy ppa wheezy\-contrib
$ aptly publish source add \-component=contrib wheezy ppa wheezy\-contrib
.
.fi
.
@@ -1752,11 +1815,11 @@ component names to add (for multi\-component publishing, separate components wit
\-\fBprefix\fR=\.
publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR
.
.SH "DROPS STAGED SOURCE CHANGES OF PUBLISHED REPOSITORY"
.SH "DROP PENDING SOURCE COMPONENT CHANGES OF A PUBLISHED REPOSITORY"
\fBaptly\fR \fBpublish\fR \fBsource\fR \fBdrop\fR \fIdistribution\fR
.
.P
Command drops the staged source changes of the published repository\.
Remove all pending changes what would be applied with a subsequent \(cqaptly publish update\(cq\.
.
.P
Example:
@@ -1816,11 +1879,14 @@ display record in JSON format
\-\fBprefix\fR=\.
publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR
.
.SH "REMOVE SOURCE FROM STAGED SOURCE LIST OF PUBLISHED REPOSITORY"
.SH "REMOVE SOURCE COMPONENTS FROM A PUBLISHED REPO"
\fBaptly\fR \fBpublish\fR \fBsource\fR \fBremove\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fIsource\fR
.
.P
The command removes sources from the staged source list of the published repository\.
The command removes source components (snapshot / local repo) from a published repository\.
.
.P
This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\.
.
.P
The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be removed, e\.g\.:
@@ -1832,7 +1898,7 @@ Example:
.
.nf
$ aptly publish remove \-component=contrib,non\-free wheezy filesystem:symlink:debian
$ aptly publish source remove \-component=contrib,non\-free wheezy filesystem:symlink:debian
.
.fi
.
@@ -1849,11 +1915,14 @@ component names to remove (for multi\-component publishing, separate components
\-\fBprefix\fR=\.
publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR
.
.SH "UPDATE SOURCE IN STAGED SOURCE LIST OF PUBLISHED REPOSITORY"
\fBaptly\fR \fBpublish\fR \fBsource\fR \fBupdate\fR \fIdistribution\fR \fIsource\fR
.SH "REPLACE THE SOURCE COMPONENTS OF A PUBLISHED REPOSITORY"
\fBaptly\fR \fBpublish\fR \fBsource\fR \fBreplace\fR \fIdistribution\fR \fIsource\fR
.
.P
The command updates sources in the staged source list of the published repository\.
The command replaces the source components of a snapshot or local repository to be published\.
.
.P
This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\.
.
.P
The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be modified\. The number of given components must be equal to the number of given sources, e\.g\.:
@@ -1862,7 +1931,7 @@ The flag \-component is mandatory\. Use a comma\-separated list of components, i
.
.nf
aptly publish update \-component=main,contrib wheezy wheezy\-main wheezy\-contrib
aptly publish source replace \-component=main,contrib wheezy wheezy\-main wheezy\-contrib
.
.fi
.
@@ -1875,7 +1944,53 @@ Example:
.
.nf
$ aptly publish update \-component=contrib wheezy ppa wheezy\-contrib
$ aptly publish source replace \-component=contrib wheezy ppa wheezy\-contrib
.
.fi
.
.IP "" 0
.
.P
Options:
.
.TP
\-\fBcomponent\fR=
component names to add (for multi\-component publishing, separate components with commas)
.
.TP
\-\fBprefix\fR=\.
publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR
.
.SH "UPDATE THE SOURCE COMPONENTS OF A PUBLISHED REPOSITORY"
\fBaptly\fR \fBpublish\fR \fBsource\fR \fBupdate\fR \fIdistribution\fR \fIsource\fR
.
.P
The command updates the source components of a snapshot or local repository to be published\.
.
.P
This does not publish the changes directly, but rather schedules them for a subsequent \(cqaptly publish update\(cq\.
.
.P
The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be modified\. The number of given components must be equal to the number of given sources, e\.g\.:
.
.IP "" 4
.
.nf
aptly publish source update \-component=main,contrib wheezy wheezy\-main wheezy\-contrib
.
.fi
.
.IP "" 0
.
.P
Example:
.
.IP "" 4
.
.nf
$ aptly publish source update \-component=contrib wheezy ppa wheezy\-contrib
.
.fi
.
@@ -1982,11 +2097,40 @@ don\(cqt generate Contents indexes
\-\fBskip\-signing\fR
don\(cqt sign Release files with GPG
.
.SH "UPDATE PUBLISHED LOCAL REPOSITORY"
.SH "UPDATE PUBLISHED REPOSITORY"
\fBaptly\fR \fBpublish\fR \fBupdate\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR]
.
.P
Command re\-publishes (updates) published local repository\. \fIdistribution\fR and \fIprefix\fR should be occupied with local repository published using command aptly publish repo\. Update happens in\-place with minimum possible downtime for published repository\.
The command updates updates a published repository after applying pending changes to the sources\.
.
.P
For published local repositories:
.
.IP "" 4
.
.nf
* update to match local repository contents
.
.fi
.
.IP "" 0
.
.P
For published snapshots:
.
.IP "" 4
.
.nf
* switch components to new snapshot
.
.fi
.
.IP "" 0
.
.P
The update happens in\-place with minimum possible downtime for published repository\.
.
.P
For multiple component published repositories, all local repositories are updated\.
@@ -2230,8 +2374,15 @@ Example:
.P
$ aptly config show
.
.P
Options:
.
.TP
\-\fByaml\fR
show yaml config
.
.SH "RUN APTLY TASKS"
\fBaptly\fR \fBtask\fR \fBrun\fR \-filename=\fIfilename\fR \fB|\fR \fIcommand1\fR, \fIcommand2\fR, \fB\|\.\|\.\|\.\fR
\fBaptly\fR \fBtask\fR \fBrun\fR (\-filename=\fIfilename\fR \fB|\fR \fIcommands\fR\|\.\|\.\|\.)
.
.P
Command helps organise multiple aptly commands in one single aptly task, running as single thread\.
@@ -2273,6 +2424,13 @@ Example:
.P
$ aptly config show
.
.P
Options:
.
.TP
\-\fByaml\fR
show yaml config
.
.SH "ENVIRONMENT"
If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\.
.
@@ -2484,7 +2642,16 @@ Golf Hu (https://github\.com/hudeng\-go)
Cookie Fei (https://github\.com/wuhuang26)
.
.IP "\[ci]" 4
Andrey Loukhnov (https://github\.com/aol\-nnov)
.
.IP "\[ci]" 4
Christoph Fiehe (https://github\.com/cfiehe)
.
.IP "\[ci]" 4
Blake Kostner (https://github\.com/btkostner)
.
.IP "\[ci]" 4
Leigh London (https://github\.com/leighlondon)
.
.IP "" 0
+356 -308
View File
@@ -18,336 +18,384 @@ aptly has integrated help that matches contents of this manual page, to get help
## CONFIGURATION
aptly looks for configuration file first in `~/.aptly.conf` then
in `/usr/local/etc/aptly.conf` and `/etc/aptly.conf`. If no config file found (or they are not readable), a new one is created in the
home directory. If `-config=` flag is specified, aptly would use config file at specified
location. Also aptly needs root directory for database, package and published repository storage.
If not specified, directory defaults to `~/.aptly/`, it will be created if missing.
aptly looks for configuration file first in `~/.aptly.conf` then in `/usr/local/etc/aptly.conf` and `/etc/aptly.conf`. If no config file found (or they are not readable), a new one is created in the
home directory. If `-config=` flag is specified, aptly would use config file at specified location. Also aptly needs root directory for database, package and published repository storage. If not specified, directory defaults to `~/.aptly/`, it will be created if missing.
Configuration file is stored in JSON format (default values shown below):
With aptly version 1.6.0, yaml configuration with inline documentation is supported and recommended (see `debian/aptly.conf`).
The legacy json configuration is still supported:
// vim: : filetype=json
// json configuration file with comments
// validate with: sed '/\/\//d' aptly.conf | json_pp
{
"rootDir": "$HOME/.aptly",
"databaseBackend": {
"type": "",
"url": ""
},
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"downloadRetries": 0,
"downloader": "default",
"databaseOpenAttempts": 10,
// Aptly Configuration File
////////////////////////////
// Root directory for:
// - downloaded packages (`rootDir`/pool)
// - database (`rootDir`/db)
// - published repositories (`rootDir`/public)
"rootDir": "~/.aptly",
// Number of attempts to open database if it's locked by other instance
// * -1 (no retry)
"databaseOpenAttempts": -1,
// Log Level
// * debug
// * info
// * warning
// * error
"logLevel": "info",
// Log Format
// * default (text)
// * json
"logFormat": "default",
// Default Architectures
// empty array defaults to all available architectures
"architectures": [],
// Follow contents of `Suggests:` field when processing dependencies for the package
"dependencyFollowSuggests": false,
// Follow contents of `Recommends:` field when processing dependencies for the package
"dependencyFollowRecommends": false,
// When dependency looks like `package-a | package-b`, follow both variants always
"dependencyFollowAllVariants": false,
// Follow dependency from binary package to source package
"dependencyFollowSource": false,
// Log additional details while resolving dependencies (useful for debugging)
"dependencyVerboseResolve": false,
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgProvider": "gpg",
"downloadSourcePackages": false,
"packagePoolStorage": {
"path": "$ROOTDIR/pool",
"azure": {
"accountName": "",
"accountKey": "",
"container": "repo",
"prefix": "",
"endpoint": ""
}
},
"skipLegacyPool": true,
// Specifies paramaters for short PPA url expansion
// empty defaults to output of `lsb_release` command
"ppaDistributorID": "ubuntu",
// Codename for short PPA url expansion
"ppaCodename": "",
// OBSOLETE
// in aptly up to version 1.0.0, package files were stored in internal package pool
// with MD5-dervied path, since 1.1.0 package pool layout was changed;
// if option is enabled, aptly stops checking for legacy paths;
// by default option is enabled for new aptly installations and disabled when
// upgrading from older versions
"skipLegacyPool": true,
// Aptly Server
////////////////
// Serve published repos as well as API
"serveInAPIMode": false,
// Enable metrics for Prometheus client
"enableMetricsEndpoint": false,
// Enable API documentation on /docs
"enableSwaggerEndpoint": false,
// OBSOLETE: use via url param ?_async=true
"AsyncAPI": false,
// Database
////////////
// Database backend
// Type must be one of:
// * leveldb (default)
// * etcd
"databaseBackend": {
// LevelDB
"type": "leveldb",
// Path to leveldb files
// empty dbPath defaults to `rootDir`/db
"dbPath": ""
// // etcd
// "type": "etcd",
// // URL to db server
// "url": "127.0.0.1:2379"
},
// Mirroring
/////////////
// Downloader
// * "default"
// * "grab" (more robust)
"downloader": "default",
// Number of parallel download threads to use when downloading packages
"downloadConcurrency": 4,
// Limit in kbytes/sec on download speed while mirroring remote repositories
"downloadSpeedLimit": 0,
// Number of retries for download attempts
"downloadRetries": 0,
// Download source packages per default
"downloadSourcePackages": false,
// Signing
///////////
// GPG Provider
// * "internal" (Go internal implementation)
// * "gpg" (External `gpg` utility)
"gpgProvider": "gpg",
// Disable signing of published repositories
"gpgDisableSign": false,
// Disable signature verification of remote repositories
"gpgDisableVerify": false,
// Publishing
//////////////
// Do not publish Contents files
"skipContentsPublishing": false,
// Do not create bz2 files
"skipBz2Publishing": false,
// Storage
///////////
// Filesystem publishing endpoints
//
// aptly defaults to publish to a single publish directory under `rootDir`/public. For
// a more advanced publishing strategy, you can define one or more filesystem endpoints in the
// `FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name
// and the following associated settings.
//
// In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name`
// with `endpoint-name` as the name given in the aptly configuration file. For example:
//
// `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily`
//
"FileSystemPublishEndpoints": {
"test1": {
"rootDir": "/opt/srv1/aptly_public",
"linkMethod": "symlink"
},
"test2": {
"rootDir": "/opt/srv2/aptly_public",
"linkMethod": "copy",
"verifyMethod": "md5"
},
"test3": {
"rootDir": "/opt/srv3/aptly_public",
"linkMethod": "hardlink"
}
// // Endpoint Name
// "test1": {
// // Directory for publishing
// "rootDir": "/opt/srv/aptly_public",
// // File Link Method for linking files from the internal pool to the published directory
// // * hardlink
// // * symlink
// // * copy
// "linkMethod": "hardlink",
// // File Copare Method for comparing existing links from the internal pool to the published directory
// // Only used when "linkMethod" is set to "copy"
// // * md5 (default: compare md5 sum)
// // * size (compare file size)
// "verifyMethod": "md5"
// }
},
// S3 Endpoint Support
//
// cloud storage). First, publishing
// endpoints should be described in aptly configuration file. Each endpoint has name
// and associated settings.
//
// In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
// publishing prefix on the command line, e.g.:
//
// `aptly publish snapshot wheezy-main s3:test:`
//
"S3PublishEndpoints": {
"test": {
"region": "us-east-1",
"bucket": "repo",
"endpoint": "",
"awsAccessKeyID": "",
"awsSecretAccessKey": "",
"prefix": "",
"acl": "public-read",
"storageClass": "",
"encryptionMethod": "",
"plusWorkaround": false,
"disableMultiDel": false,
"forceSigV2": false,
"forceVirtualHostedStyle": true,
"debug": false
}
// // Endpoint Name
// "test": {
// // Amazon region for S3 bucket
// "region": "us-east-1",
// // Bucket name
// "bucket": "test-bucket",
// // Endpoint (optional)
// // When using S3-compatible cloud storage, specify hostname of service endpoint here,
// // region is ignored if endpoint is set (set region to some human-readable name)
// // (should be left blank for real Amazon S3)
// "endpoint": "",
// // Prefix (optional)
// // publishing under specified prefix in the bucket, defaults to
// // no prefix (bucket root)
// "prefix": "",
// // Default ACLs (optional)
// // assign ACL to published files (one of the canned ACLs in Amazon
// // terminology). Useful values: `private` (default), `public-read` (public
// // repository) or `none` (don't set ACL). Public repositories could be consumed by `apt` using
// // HTTP endpoint (Amazon bucket should be configured for "website hosting"),
// // for private repositories special apt S3 transport is required.
// "acl": "private",
// // Credentials (optional)
// // Amazon credentials to access S3 bucket. If not supplied,
// // environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
// // are used.
// "awsAccessKeyID": "",
// "awsSecretAccessKey": "",
// // Storage Class (optional)
// // Amazon S3 storage class, defaults to `STANDARD`. Other values
// // available: `REDUCED_REDUNDANCY` (lower price, lower redundancy)
// "storageClass": "STANDARD",
// // Encryption Method (optional)
// // Server-side encryption method, defaults to none. Currently
// // the only available encryption method is `AES256`
// "encryptionMethod": "none",
// // Plus Workaround (optional)
// // Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by
// // creating two copies of package files with `+` in filename: one original
// // and another one with spaces instead of plus signs
// // With `plusWorkaround` enabled, package files with plus sign
// // would be stored twice. aptly might not cleanup files with spaces when published
// // repository is dropped or updated (switched) to new version of repository (snapshot)
// "plusWorkaround": false,
// // Disable MultiDel (optional)
// // For S3-compatible cloud storages which do not support `MultiDel` S3 API,
// // enable this setting (file deletion would be slower with this setting enabled)
// "disableMultiDel": false,
// // ForceSig2 (optional)
// // Disable Signature V4 support, useful with non-AWS S3-compatible object stores
// // which do not support SigV4, shouldn't be enabled for AWS
// "forceSigV2": false,
// // ForceVirtualHostedStyle (optional)
// // Disable path style visit, useful with non-AWS S3-compatible object stores
// // which only support virtual hosted style
// "forceVirtualHostedStyle": false,
// // Debug (optional)
// // Enables detailed request/response dump for each S3 operation
// "debug": false
// }
},
// Swift Endpoint Support
//
// aptly could be configured to publish repository directly to OpenStack Swift. First,
// publishing endpoints should be described in aptly configuration file. Each endpoint
// has name and associated settings.
//
// In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before
// publishing prefix on the command line, e.g.:
//
// `aptly publish snapshot jessie-main swift:test:`
//
"SwiftPublishEndpoints": {
"test": {
"container": "repo",
"osname": "",
"password": "",
"prefix": "",
"authurl": "",
"tenant": "",
"tenantid": ""
}
// Endpoint Name
// "test": {
// // Container Name
// "container": "taylor1",
// // Prefix (optional)
// // Publish under specified prefix in the container, defaults to no prefix (container root)
// "prefix": "",
// // Credentials (optional)
// // OpenStack credentials to access Keystone. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used
// "osname": "",
// "password": "",
// // Tenant (optional)
// // OpenStack tenant name and id (in order to use v2 authentication)
// "tenant": "",
// "tenantid": "",
// // Auth URL (optional)
// // Full url of Keystone server (including port, and version).
// // Example `http://identity.example.com:5000/v2.0`
// "authurl": ""
// }
},
// Azure Endpoint Support
//
// aptly can be configured to publish repositories directly to Microsoft Azure Blob
// Storage. First, publishing endpoints should be described in the aptly
// configuration file. Each endpoint has its name and associated settings.
"AzurePublishEndpoints": {
"test": {
"accountName": "",
"accountKey": "",
"container": "repo",
"prefix": "",
"endpoint": "blob.core.windows.net"
}
// // Endpoint Name
// "test": {
// // Container Name
// "container": "container1",
// // Prefix (optional)
// // Publishing under specified prefix in the container, defaults to no prefix (container root)
// "prefix": "",
// // Credentials
// // Azure storage account access key to access blob storage
// "accountName": "",
// "accountKey": "",
// // Endpoint URL
// // See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
// // defaults to "https://<accountName>.blob.core.windows.net"
// "endpoint": ""
// }
},
// Package Pool
// Location for storing downloaded packages
// Type must be one of:
// * local
// * azure
"packagePoolStorage": {
// Local Pool
"type": "local",
// Local Pool Path
// empty path defaults to `rootDir`/pool
"path": ""
// // Azure Azure Blob Storage Pool
// "type": "azure",
// "container": "pool1",
// // Prefix (optional)
// // Publishing under specified prefix in the container, defaults to no prefix (container root)
// "prefix": "",
// // Credentials
// // Azure storage account access key to access blob storage
// "accountName": "",
// "accountKey": "",
// // Endpoint URL
// // See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
// // defaults to "https://<accountName>.blob.core.windows.net"
// "endpoint": ""
}
// End of config
}
Options:
* `rootDir`:
is root of directory storage to store database (`rootDir`/db),
the default for downloaded packages (`rootDir`/pool) and
the default for published repositories (`rootDir`/public) and
skeleton files (`rootDir`/skel)
* `databaseBackend`:
the database config; if this config is empty, use levledb backend by default
* `downloadConcurrency`:
is a number of parallel download threads to use when downloading packages
* `downloadSpeedLimit`:
limit in kbytes/sec on download speed while mirroring remote repositories
* `downloadRetries`:
number of retries for download attempts
* `databaseOpenAttempts`:
number of attempts to open DB if it's locked by other instance; could be overridden with option
`-db-open-attempts`
* `architectures`:
is a list of architectures to process; if left empty defaults to all available architectures; could be
overridden with option `-architectures`
* `dependencyFollowSuggests`:
follow contents of `Suggests:` field when processing dependencies for the package
* `dependencyFollowRecommends`:
follow contents of `Recommends:` field when processing dependencies for the package
* `dependencyFollowAllVariants`:
when dependency looks like `package-a | package-b`, follow both variants always
* `dependencyFollowSource`:
follow dependency from binary package to source package
* `dependencyVerboseResolve`:
print additional details while resolving dependencies (useful for debugging)
* `gpgDisableSign`:
don't sign published repositories with gpg(1), also can be disabled on
per-repo basis using `-skip-signing` flag when publishing
* `gpgDisableVerify`:
don't verify remote mirrors with gpg(1), also can be disabled on
per-mirror basis using `-ignore-signatures` flag when creating and updating mirrors
* `gpgProvider`:
implementation of PGP signing/validation - `gpg` for external `gpg` utility or
`internal` to use Go internal implementation; `gpg1` might be used to force use
of GnuPG 1.x, `gpg2` enables GnuPG 2.x only; default is to use GnuPG 1.x if
available and GnuPG 2.x otherwise
* `downloadSourcePackages`:
if enabled, all mirrors created would have flag set to download source packages;
this setting could be controlled on per-mirror basis with `-with-sources` flag
* `packagePoolStorage`:
configures the location to store downloaded packages (defaults to the
path `$ROOTDIR/pool`), by setting the value of the `type`:
* `path`: store the packages in the given path
* `azure`: store the packages in the given Azure Blob Storage container
(see the section on Azure publishing below for information on the
configuration)
* `skipLegacyPool`:
in aptly up to version 1.0.0, package files were stored in internal package pool
with MD5-dervied path, since 1.1.0 package pool layout was changed;
if option is enabled, aptly stops checking for legacy paths;
by default option is enabled for new aptly installations and disabled when
upgrading from older versions
* `ppaDistributorID`, `ppaCodename`:
specifies paramaters for short PPA url expansion, if left blank they default
to output of `lsb_release` command
* `FileSystemPublishEndpoints`:
configuration of local filesystem publishing endpoints (see below)
* `S3PublishEndpoints`:
configuration of Amazon S3 publishing endpoints (see below)
* `SwiftPublishEndpoints`:
configuration of OpenStack Swift publishing endpoints (see below)
* `AzurePublishEndpoints`:
configuration of Azure publishing endpoints (see below)
## CUSTOM PACKAGE POOLS
aptly defaults to storing downloaded packages at `rootDir/`pool. In order to
change this, you can set the `type` key within `packagePoolStorage` to one of
two values:
* `local`: Store the package pool locally (the default). In order to change
the path, additionally set the `path` key within `packagePoolStorage` to
the desired location.
* `azure`: Store the package pool in an Azure Blob Storage container. Any
keys in the below section on Azure publishing may be set on the
`packagePoolStorage` object in order to configure the Azure connection.
## FILESYSTEM PUBLISHING ENDPOINTS
aptly defaults to publish to a single publish directory under `rootDir`/public. For
a more advanced publishing strategy, you can define one or more filesystem endpoints in the
`FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name
and the following associated settings:
* `rootDir`:
The publish directory, e.g., `/opt/srv/aptly_public`.
* `linkMethod`:
This is one of `hardlink`, `symlink` or `copy`. It specifies how aptly links the
files from the internal pool to the published directory.
If not specified, empty or wrong, this defaults to `hardlink`.
* `verifyMethod`:
This is used only when setting the `linkMethod` to `copy`. Possible values are
`md5` and `size`. It specifies how aptly compares existing links from the
internal pool to the published directory. The `size` method compares only the
file sizes, whereas the `md5` method calculates the md5 checksum of the found
file and compares it to the desired one.
If not specified, empty or wrong, this defaults to `md5`.
In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name`
with `endpoint-name` as the name given in the aptly configuration file. For example:
`aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily`
## S3 PUBLISHING ENDPOINTS
aptly could be configured to publish repository directly to Amazon S3 (or S3-compatible
cloud storage). First, publishing
endpoints should be described in aptly configuration file. Each endpoint has name
and associated settings:
* `region`:
Amazon region for S3 bucket (e.g. `us-east-1`)
* `bucket`:
bucket name
* `endpoint`:
(optional) when using S3-compatible cloud storage, specify hostname of service endpoint here,
region is ignored if endpoint is set (set region to some human-readable name)
(should be left blank for real Amazon S3)
* `prefix`:
(optional) do publishing under specified prefix in the bucket, defaults to
no prefix (bucket root)
* `acl`:
(optional) assign ACL to published files (one of the canned ACLs in Amazon
terminology). Useful values: `private` (default), `public-read` (public
repository) or `none` (don't set ACL). Public repositories could be consumed by `apt` using
HTTP endpoint (Amazon bucket should be configured for "website hosting"),
for private repositories special apt S3 transport is required.
* `awsAccessKeyID`, `awsSecretAccessKey`:
(optional) Amazon credentials to access S3 bucket. If not supplied,
environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
are used.
* `storageClass`:
(optional) Amazon S3 storage class, defaults to `STANDARD`. Other values
available: `REDUCED_REDUNDANCY` (lower price, lower redundancy)
* `encryptionMethod`:
(optional) server-side encryption method, defaults to none. Currently
the only available encryption method is `AES256`
* `plusWorkaround`:
(optional) workaround misbehavior in apt and Amazon S3
for files with `+` in filename by
creating two copies of package files with `+` in filename: one original
and another one with spaces instead of plus signs
With `plusWorkaround` enabled, package files with plus sign
would be stored twice. aptly might not cleanup files with spaces when published
repository is dropped or updated (switched) to new version of repository (snapshot)
* `disableMultiDel`:
(optional) for S3-compatible cloud storages which do not support `MultiDel` S3 API,
enable this setting (file deletion would be slower with this setting enabled)
* `forceSigV2`:
(optional) disable Signature V4 support, useful with non-AWS S3-compatible object stores
which do not support SigV4, shouldn't be enabled for AWS
* `forceVirtualHostedStyle`:
(optional) disable path style visit, useful with non-AWS S3-compatible object stores
which only support virtual hosted style
* `debug`:
(optional) enables detailed request/response dump for each S3 operation
In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
publishing prefix on the command line, e.g.:
`aptly publish snapshot wheezy-main s3:test:`
## OPENSTACK SWIFT PUBLISHING ENDPOINTS
aptly could be configured to publish repository directly to OpenStack Swift. First,
publishing endpoints should be described in aptly configuration file. Each endpoint
has name and associated settings:
* `container`:
container name
* `prefix`:
(optional) do publishing under specified prefix in the container, defaults to
no prefix (container root)
* `osname`, `password`:
(optional) OpenStack credentials to access Keystone. If not supplied,
environment variables `OS_USERNAME` and `OS_PASSWORD` are used.
* `tenant`, `tenantid`:
(optional) OpenStack tenant name and id (in order to use v2 authentication).
* `authurl`:
(optional) the full url of Keystone server (including port, and version).
example `http://identity.example.com:5000/v2.0`
In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before
publishing prefix on the command line, e.g.:
`aptly publish snapshot jessie-main swift:test:`
## AZURE PUBLISHING ENDPOINTS
aptly can be configured to publish repositories directly to Microsoft Azure Blob
Storage. First, publishing endpoints should be described in the aptly
configuration file. Each endpoint has its name and associated settings:
* `container`:
container name
* `prefix`:
(optional) do publishing under specified prefix in the container, defaults to
no prefix (container root)
* `accountName`, `accountKey`:
Azure storage account access key to access blob storage
* `endpoint`:
endpoint URL to connect to, as described in
[the Azure documentation](https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string);
defaults to `https://$accountName.blob.core.windows.net`
## PACKAGE QUERY
+1 -1
View File
@@ -84,7 +84,7 @@ class APITest(BaseTest):
self._ensure_async(kwargs)
resp = self.post(uri, *args, **kwargs)
if resp.status_code != 202:
return resp
raise Exception("async api error: " + resp.text)
_id = resp.json()['ID']
resp = self.get("/api/tasks/" + str(_id) + "/wait")
+4 -4
View File
@@ -242,7 +242,7 @@ class BaseTest(object):
os.environ["HOME"], self.aptlyDir, "pool"), ignore=shutil.ignore_patterns(".git"))
if self.databaseType == "etcd":
if not os.path.exists("/srv/etcd"):
if not os.path.exists("/tmp/aptly-etcd"):
self.run_cmd([os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "t13_etcd/install-etcd.sh")])
if self.fixtureDB and self.databaseType != "etcd":
@@ -253,12 +253,12 @@ class BaseTest(object):
self.shutdown_etcd()
# remove existing database
if os.path.exists("/tmp/etcd-data"):
shutil.rmtree("/tmp/etcd-data")
if os.path.exists("/tmp/aptly-etcd-data"):
shutil.rmtree("/tmp/aptly-etcd-data")
if self.fixtureDB:
print("import etcd")
self.run_cmd(["/srv/etcd/etcdctl", "--data-dir=/tmp/etcd-data", "snapshot", "restore", os.path.join(os.environ["HOME"], "etcd.db")])
self.run_cmd(["/tmp/aptly-etcd/etcdctl", "--data-dir=/tmp/aptly-etcd-data", "snapshot", "restore", os.path.join(os.environ["HOME"], "etcd.db")])
print("starting etcd")
self.EtcdServer = self._start_process([os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "t13_etcd/start-etcd.sh")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+1 -1
View File
@@ -1 +1 @@
ERROR: error loading config file ${HOME}/.aptly.conf: invalid character 's' looking for beginning of object key string
ERROR: error loading config file ${HOME}/.aptly.conf: invalid yaml (yaml: line 1: did not find expected ',' or '}') or json (invalid character 's' looking for beginning of object key string)
+21 -21
View File
@@ -1,39 +1,39 @@
{
"rootDir": "${HOME}/.aptly",
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"downloadRetries": 5,
"downloader": "default",
"logLevel": "debug",
"logFormat": "default",
"databaseOpenAttempts": 10,
"architectures": [],
"skipLegacyPool": false,
"dependencyFollowSuggests": false,
"dependencyFollowRecommends": false,
"dependencyFollowAllVariants": false,
"dependencyFollowSource": false,
"dependencyVerboseResolve": false,
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgProvider": "gpg",
"downloadSourcePackages": false,
"packagePoolStorage": {},
"skipLegacyPool": false,
"ppaDistributorID": "ubuntu",
"ppaCodename": "",
"serveInAPIMode": true,
"enableMetricsEndpoint": true,
"enableSwaggerEndpoint": false,
"AsyncAPI": false,
"databaseBackend": {
"type": "",
"dbPath": "",
"url": ""
},
"downloader": "default",
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"downloadRetries": 5,
"downloadSourcePackages": false,
"gpgProvider": "gpg",
"gpgDisableSign": false,
"gpgDisableVerify": false,
"skipContentsPublishing": false,
"skipBz2Publishing": false,
"FileSystemPublishEndpoints": {},
"S3PublishEndpoints": {},
"SwiftPublishEndpoints": {},
"AzurePublishEndpoints": {},
"AsyncAPI": false,
"enableMetricsEndpoint": true,
"logLevel": "debug",
"logFormat": "default",
"serveInAPIMode": true,
"databaseBackend": {
"type": "",
"url": "",
"dbPath": ""
},
"enableSwaggerEndpoint": false
"packagePoolStorage": {}
}
+37
View File
@@ -0,0 +1,37 @@
root_dir: ${HOME}/.aptly
log_level: debug
log_format: default
database_open_attempts: 10
architectures: []
skip_legacy_pool: false
dep_follow_suggests: false
dep_follow_recommends: false
dep_follow_all_variants: false
dep_follow_source: false
dep_verboseresolve: false
ppa_distributor_id: ubuntu
ppa_codename: ""
serve_in_api_mode: true
enable_metrics_endpoint: true
enable_swagger_endpoint: false
async_api: false
database_backend:
type: ""
db_path: ""
url: ""
downloader: default
download_concurrency: 4
download_limit: 0
download_retries: 5
download_sourcepackages: false
gpg_provider: gpg
gpg_disable_sign: false
gpg_disable_verify: false
skip_contents_publishing: false
skip_bz2_publishing: false
filesystem_publish_endpoints: {}
s3_publish_endpoints: {}
swift_publish_endpoints: {}
azure_publish_endpoints: {}
packagepool_storage: {}
+343 -39
View File
@@ -1,39 +1,343 @@
{
"rootDir": "${HOME}/.aptly",
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"downloadRetries": 0,
"downloader": "default",
"databaseOpenAttempts": -1,
"architectures": [],
"dependencyFollowSuggests": false,
"dependencyFollowRecommends": false,
"dependencyFollowAllVariants": false,
"dependencyFollowSource": false,
"dependencyVerboseResolve": false,
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgProvider": "gpg",
"downloadSourcePackages": false,
"packagePoolStorage": {},
"skipLegacyPool": true,
"ppaDistributorID": "ubuntu",
"ppaCodename": "",
"skipContentsPublishing": false,
"skipBz2Publishing": false,
"FileSystemPublishEndpoints": {},
"S3PublishEndpoints": {},
"SwiftPublishEndpoints": {},
"AzurePublishEndpoints": {},
"AsyncAPI": false,
"enableMetricsEndpoint": false,
"logLevel": "debug",
"logFormat": "default",
"serveInAPIMode": false,
"databaseBackend": {
"type": "",
"url": "",
"dbPath": ""
},
"enableSwaggerEndpoint": false
}
# Aptly Configuration File
###########################
# vim: : filetype=yaml
# aptly 1.6.0 supports yaml configuraiton files with inline documentation and examples.
# Legacy json config files are still supported, and may be converted to yaml with `aptly config show -yaml`
# Root directory for:
# - downloaded packages (`rootDir`/pool)
# - database (`rootDir`/db)
# - published repositories (`rootDir`/public)
root_dir: ~/.aptly
# Log Level
# * debug
# * info
# * warning
# * error
log_level: info
# Log Format
# * default (text)
# * json
log_format: default
# Number of attempts to open database if it's locked by other instance
# * -1 (no retry)
database_open_attempts: -1
# Default Architectures
# empty list defaults to all available architectures
architectures:
# - amd64
# OBSOLETE
# in aptly up to version 1.0.0, package files were stored in internal package pool
# with MD5-dervied path, since 1.1.0 package pool layout was changed;
# if option is enabled, aptly stops checking for legacy paths;
# by default option is enabled for new aptly installations and disabled when
# upgrading from older versions
skip_legacy_pool: true
# Dependency following
#######################
# Follow contents of `Suggests:` field when processing dependencies for the package
dep_follow_suggests: false
# Follow contents of `Recommends:` field when processing dependencies for the package
dep_follow_recommends: false
# When dependency looks like `package-a | package-b`, follow both variants always
dep_follow_allvariants: false
# Follow dependency from binary package to source package
dep_follow_source: false
# Log additional details while resolving dependencies (useful for debugging)
dep_verbose_resolve: false
# PPA
######
# Specify paramaters for short PPA url expansion
# empty defaults to output of `lsb_release` command
ppa_distributor_id: ubuntu
# Codename for short PPA url expansion
ppa_codename: ""
# Aptly Server
###############
# Serve published repos as well as API
serve_in_api_mode: false
# Enable metrics for Prometheus client
enable_metrics_endpoint: false
# Enable API documentation on /docs
enable_swagger_endpoint: false
# OBSOLETE: use via url param ?_async=true
async_api: false
# Database
###########
# Database backend
# Type must be one of:
# * leveldb (default)
# * etcd
database_backend:
type: leveldb
# Path to leveldb files
# empty dbPath defaults to `rootDir`/db
db_path: ""
# type: etcd
# # URL to db server
# url: "127.0.0.1:2379"
# Mirroring
############
# Downloader
# * "default"
# * "grab" (more robust)
downloader: default
# Number of parallel download threads to use when downloading packages
download_concurrency: 4
# Limit in kbytes/sec on download speed while mirroring remote repositories
download_limit: 0
# Number of retries for download attempts
download_retries: 0
# Download source packages per default
download_sourcepackages: false
# Signing
##########
# GPG Provider
# * "internal" (Go internal implementation)
# * "gpg" (External `gpg` utility)
gpg_provider: gpg
# Disable signing of published repositories
gpg_disable_sign: false
# Disable signature verification of remote repositories
gpg_disable_verify: false
# Publishing
#############
# Do not publish Contents files
skip_contents_publishing: false
# Do not create bz2 files
skip_bz2_publishing: false
# Storage
##########
# Filesystem publishing endpoints
#
# aptly defaults to publish to a single publish directory under `rootDir`/public. For
# a more advanced publishing strategy, you can define one or more filesystem endpoints in the
# `FileSystemPublishEndpoints` list of the aptly configuration file. Each endpoint has a name
# and the following associated settings.
#
# In order to publish to such an endpoint, specify the endpoint as `filesystem:endpoint-name`
# with `endpoint-name` as the name given in the aptly configuration file. For example:
#
# `aptly publish snapshot wheezy-main filesystem:test1:wheezy/daily`
#
filesystem_publish_endpoints:
# # Endpoint Name
# test1:
# # Directory for publishing
# root_dir: /opt/srv/aptly_public
# # File Link Method for linking files from the internal pool to the published directory
# # * hardlink
# # * symlink
# # * copy
# link_method: hardlink
# # File Copare Method for comparing existing links from the internal pool to the published directory
# # Only used when "linkMethod" is set to "copy"
# # * md5 (default: compare md5 sum)
# # * size (compare file size)
# verify_method: md5
# S3 Endpoint Support
#
# cloud storage). First, publishing
# endpoints should be described in aptly configuration file. Each endpoint has name
# and associated settings.
#
# In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
# publishing prefix on the command line, e.g.:
#
# `aptly publish snapshot wheezy-main s3:test:`
#
s3_publish_endpoints:
# # Endpoint Name
# test:
# # Amazon region for S3 bucket
# region: us-east-1
# # Bucket name
# bucket: test-bucket
# # Prefix (optional)
# # publishing under specified prefix in the bucket, defaults to
# # no prefix (bucket root)
# prefix: ""
# # Default ACLs (optional)
# # assign ACL to published files:
# # * private (default, for use with apt S3 transport)
# # * public-read (public repository)
# # * none (don't set ACL)
# acl: private
# # Credentials (optional)
# # Amazon credentials to access S3 bucket. If not supplied, environment variables
# # `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` are used
# access_key_id: ""
# secret_access_key: ""
# session_token: ""
# # Endpoint (optional)
# # When using S3-compatible cloud storage, specify hostname of service endpoint here,
# # region is ignored if endpoint is set (set region to some human-readable name)
# # (should be left blank for real Amazon S3)
# endpoint: ""
# # Storage Class (optional)
# # Amazon S3 storage class, defaults to `STANDARD`. Other values
# # available: `REDUCED_REDUNDANCY` (lower price, lower redundancy)
# storage_class: STANDARD
# # Encryption Method (optional)
# # Server-side encryption method, defaults to none. Currently
# # the only available encryption method is `AES256`
# encryption_method: none
# # Plus Workaround (optional)
# # Workaround misbehavior in apt and Amazon S3 for files with `+` in filename by
# # creating two copies of package files with `+` in filename: one original
# # and another one with spaces instead of plus signs
# # With `plusWorkaround` enabled, package files with plus sign
# # would be stored twice. aptly might not cleanup files with spaces when published
# # repository is dropped or updated (switched) to new version of repository (snapshot)
# plus_workaround: false
# # Disable MultiDel (optional)
# # For S3-compatible cloud storages which do not support `MultiDel` S3 API,
# # enable this setting (file deletion would be slower with this setting enabled)
# disable_multidel: false
# # Force Signature v2 (optional)
# # Disable Signature V4 support, useful with non-AWS S3-compatible object stores
# # which do not support SigV4, shouldn't be enabled for AWS
# force_sigv2: false
# # Force VirtualHosted Style (optional)
# # Disable path style visit, useful with non-AWS S3-compatible object stores
# # which only support virtual hosted style
# force_virtualhosted_style: false
# # Debug (optional)
# # Enables detailed request/response dump for each S3 operation
# debug: false
# Swift Endpoint Support
#
# aptly can publish a repository directly to OpenStack Swift.
# Each endpoint has name and associated settings.
#
# In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before
# publishing prefix on the command line, e.g.:
#
# `aptly publish snapshot jessie-main swift:test:`
#
swift_publish_endpoints:
# # Endpoint Name
# test:
# # Container Name
# container: taylor1
# # Prefix (optional)
# # Publish under specified prefix in the container, defaults to no prefix (container root)
# prefix: ""
# # Credentials (optional)
# # OpenStack credentials to access Keystone. If not supplied, environment variables `OS_USERNAME` and `OS_PASSWORD` are used
# username: ""
# password: ""
# # Domain (optional)
# # OpenStack domain
# domain: ""
# domain_id: ""
# # Tenant (optional)
# # OpenStack tenant (in order to use v2 authentication)
# tenant: ""
# tenant_id: ""
# tenant_domain: ""
# tenant_domain_id: ""
# # Auth URL (optional)
# # Full url of Keystone server (including port, and version).
# # Example `http://identity.example.com:5000/v2.0`
# auth_url: ""
# Azure Endpoint Support
#
# aptly can be configured to publish repositories directly to Microsoft Azure Blob
# Storage. First, publishing endpoints should be described in the aptly
# configuration file. Each endpoint has its name and associated settings.
azure_publish_endpoints:
# # Endpoint Name
# test:
# # Container Name
# container: container1
# # Prefix (optional)
# # Publishing under specified prefix in the container, defaults to no prefix (container root)
# prefix: ""
# # Credentials
# # Azure storage account access key to access blob storage
# account_name: ""
# account_key: ""
# # Endpoint URL
# # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
# # defaults to "https://<accountName>.blob.core.windows.net"
# endpoint: ""
# Package Pool
#
# Location for storing downloaded packages
# Type must be one of:
# * local
# * azure
packagepool_storage:
# Local Pool
type: local
# Local Pool Path
# empty path defaults to `rootDir`/pool
path:
# # Azure Azure Blob Storage Pool
# type: azure
# # Container Name
# container: pool1
# # Prefix (optional)
# # Publishing under specified prefix in the container, defaults to no prefix (container root)
# prefix: ""
# # Credentials
# # Azure storage account access key to access blob storage
# account_name: ""
# account_key: ""
# # Endpoint URL
# # See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
# # defaults to "https://<accountName>.blob.core.windows.net"
# endpoint: ""
+8
View File
@@ -64,3 +64,11 @@ class ConfigShowTest(BaseTest):
"""
runCmd = ["aptly", "config", "show"]
gold_processor = BaseTest.expand_environ
class ConfigShowYAMLTest(BaseTest):
"""
config showing
"""
runCmd = ["aptly", "config", "show", "-yaml"]
gold_processor = BaseTest.expand_environ
+8 -2
View File
@@ -2,7 +2,7 @@
Applying filter...
Building download queue...
Download queue: 11 items (39.66 MiB)
Download queue: 17 items (47.22 MiB)
Downloading & parsing package files...
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/InRelease
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Packages.bz2
@@ -16,10 +16,16 @@ Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/d
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-data_0.7.5-1~bullseyecran.0_all.deb
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_amd64.deb
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_i386.deb
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0.debian.tar.xz
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0.dsc
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_amd64.deb
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_i386.deb
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5.orig.tar.gz
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12-1~bullseyecran.0.debian.tar.xz
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12-1~bullseyecran.0.dsc
Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rpy2_3.5.12.orig.tar.gz
Mirror `flat-src` has been updated successfully.
Packages filtered: 110 -> 11.
Packages filtered: 110 -> 13.
gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41
gpgv: Good signature from "Johannes Ranke <johannes.ranke@jrwb.de>"
gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC
+20
View File
@@ -1344,6 +1344,26 @@ class PublishSnapshot41Test(BaseTest):
self.check_exists('public/pool/main/libx/libxslt/libxslt1.1_1.1.32-2.2~deb10u2_i386.deb')
self.check_exists('public/pool/main/libz/libzstd/libzstd1_1.3.8+dfsg-3+deb10u2_i386.deb')
self.check_exists('public/pool/main/z/zlib/zlib1g_1.2.11.dfsg-1+deb10u2_i386.deb')
# check source packages with different names
self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1-0.1+deb10u1.dsc')
self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1-0.1+deb10u1.debian.tar.xz')
self.check_exists('public/pool/main/u/util-linux/util-linux_2.33.1.orig.tar.xz')
self.check_exists('public/pool/main/g/glibc/glibc_2.28-10+deb10u2.debian.tar.xz')
self.check_exists('public/pool/main/g/glibc/glibc_2.28-10+deb10u2.dsc')
self.check_exists('public/pool/main/g/glibc/glibc_2.28.orig.tar.xz')
self.check_exists('public/pool/main/n/ncurses/ncurses_6.1+20181013-2+deb10u5.debian.tar.xz')
self.check_exists('public/pool/main/n/ncurses/ncurses_6.1+20181013-2+deb10u5.dsc')
self.check_exists('public/pool/main/n/ncurses/ncurses_6.1+20181013.orig.tar.gz')
self.check_exists('public/pool/main/z/zlib/zlib_1.2.11.dfsg-1+deb10u2.debian.tar.xz')
self.check_exists('public/pool/main/z/zlib/zlib_1.2.11.dfsg-1+deb10u2.dsc')
self.check_exists('public/pool/main/z/zlib/zlib_1.2.11.dfsg.orig.tar.gz')
self.check_exists('public/pool/main/x/xz-utils/xz-utils_5.2.4-1+deb10u1.debian.tar.xz')
self.check_exists('public/pool/main/x/xz-utils/xz-utils_5.2.4-1+deb10u1.dsc')
self.check_exists('public/pool/main/x/xz-utils/xz-utils_5.2.4.orig.tar.xz')
self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5-1+deb10u2.debian.tar.xz')
self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5-1+deb10u2.dsc')
self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5.orig.tar.gz')
self.check_exists('public/pool/main/e/e2fsprogs/e2fsprogs_1.44.5.orig.tar.gz.asc')
class PublishSnapshot42Test(BaseTest):
+11 -4
View File
@@ -4,9 +4,16 @@
ETCD_VER=v3.5.2
DOWNLOAD_URL=https://storage.googleapis.com/etcd
if [ ! -e /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz ]; then
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
ARCH=""
case $(uname -m) in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
*) echo "unsupported cpu arch"; exit 1 ;;
esac
if [ ! -e /tmp/etcd-${ETCD_VER}-linux-$ARCH.tar.gz ]; then
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-$ARCH.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-$ARCH.tar.gz
fi
mkdir /srv/etcd
tar xf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /srv/etcd --strip-components=1
mkdir /tmp/aptly-etcd
tar xf /tmp/etcd-${ETCD_VER}-linux-$ARCH.tar.gz -C /tmp/aptly-etcd --strip-components=1
+1 -1
View File
@@ -16,7 +16,7 @@ finish()
}
trap finish INT
/srv/etcd/etcd --max-request-bytes '1073741824' --data-dir /tmp/etcd-data &
/tmp/aptly-etcd/etcd --max-request-bytes '1073741824' --data-dir /tmp/aptly-etcd-data &
echo $! > /tmp/etcd.pid
etcdpid=`cat /tmp/etcd.pid`
wait $etcdpid
@@ -0,0 +1,2 @@
Generating graph...
Output saved to /tmp/aptly-graph.png
+2
View File
@@ -0,0 +1,2 @@
Generating graph...
Displaying png file: xdg-open /tmp/aptly-graph1173098610.png
+33
View File
@@ -0,0 +1,33 @@
"""
Test aptly graph
"""
import os
import re
from lib import BaseTest
class CreateGraphTest(BaseTest):
"""
open graph in viewer
"""
fixtureCmds = ["mkdir -p ../build", "ln -fs /bin/true ../build/xdg-open"]
environmentOverride = {"PATH": os.environ["PATH"] + ":../build"}
runCmd = "aptly graph"
def outputMatchPrepare(self, s):
return re.sub(r"[0-9]", "", s)
def teardown(self):
self.run_cmd(["rm", "-f", "../build/xdg-open"])
class CreateGraphOutputTest(BaseTest):
"""
open graph in viewer
"""
runCmd = "aptly graph -output /tmp/aptly-graph.png"
def teardown(self):
self.run_cmd(["rm", "-f", "/tmp/aptly-graph.png"])
+175 -76
View File
@@ -6,54 +6,74 @@ import (
"os"
"path/filepath"
"strings"
"github.com/DisposaBoy/JsonConfigReader"
"gopkg.in/yaml.v3"
)
// ConfigStructure is structure of main configuration
type ConfigStructure struct { // nolint: maligned
RootDir string `json:"rootDir"`
DownloadConcurrency int `json:"downloadConcurrency"`
DownloadLimit int64 `json:"downloadSpeedLimit"`
DownloadRetries int `json:"downloadRetries"`
Downloader string `json:"downloader"`
DatabaseOpenAttempts int `json:"databaseOpenAttempts"`
Architectures []string `json:"architectures"`
DepFollowSuggests bool `json:"dependencyFollowSuggests"`
DepFollowRecommends bool `json:"dependencyFollowRecommends"`
DepFollowAllVariants bool `json:"dependencyFollowAllVariants"`
DepFollowSource bool `json:"dependencyFollowSource"`
DepVerboseResolve bool `json:"dependencyVerboseResolve"`
GpgDisableSign bool `json:"gpgDisableSign"`
GpgDisableVerify bool `json:"gpgDisableVerify"`
GpgProvider string `json:"gpgProvider"`
DownloadSourcePackages bool `json:"downloadSourcePackages"`
PackagePoolStorage PackagePoolStorage `json:"packagePoolStorage"`
SkipLegacyPool bool `json:"skipLegacyPool"`
PpaDistributorID string `json:"ppaDistributorID"`
PpaCodename string `json:"ppaCodename"`
SkipContentsPublishing bool `json:"skipContentsPublishing"`
SkipBz2Publishing bool `json:"skipBz2Publishing"`
FileSystemPublishRoots map[string]FileSystemPublishRoot `json:"FileSystemPublishEndpoints"`
S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints"`
SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints"`
AzurePublishRoots map[string]AzureEndpoint `json:"AzurePublishEndpoints"`
AsyncAPI bool `json:"AsyncAPI"`
EnableMetricsEndpoint bool `json:"enableMetricsEndpoint"`
LogLevel string `json:"logLevel"`
LogFormat string `json:"logFormat"`
ServeInAPIMode bool `json:"serveInAPIMode"`
DatabaseBackend DBConfig `json:"databaseBackend"`
EnableSwaggerEndpoint bool `json:"enableSwaggerEndpoint"`
// General
RootDir string `json:"rootDir" yaml:"root_dir"`
LogLevel string `json:"logLevel" yaml:"log_level"`
LogFormat string `json:"logFormat" yaml:"log_format"`
DatabaseOpenAttempts int `json:"databaseOpenAttempts" yaml:"database_open_attempts"`
Architectures []string `json:"architectures" yaml:"architectures"`
SkipLegacyPool bool `json:"skipLegacyPool" yaml:"skip_legacy_pool"` // OBSOLETE
// Dependency following
DepFollowSuggests bool `json:"dependencyFollowSuggests" yaml:"dep_follow_suggests"`
DepFollowRecommends bool `json:"dependencyFollowRecommends" yaml:"dep_follow_recommends"`
DepFollowAllVariants bool `json:"dependencyFollowAllVariants" yaml:"dep_follow_all_variants"`
DepFollowSource bool `json:"dependencyFollowSource" yaml:"dep_follow_source"`
DepVerboseResolve bool `json:"dependencyVerboseResolve" yaml:"dep_verboseresolve"`
// PPA
PpaDistributorID string `json:"ppaDistributorID" yaml:"ppa_distributor_id"`
PpaCodename string `json:"ppaCodename" yaml:"ppa_codename"`
// Server
ServeInAPIMode bool `json:"serveInAPIMode" yaml:"serve_in_api_mode"`
EnableMetricsEndpoint bool `json:"enableMetricsEndpoint" yaml:"enable_metrics_endpoint"`
EnableSwaggerEndpoint bool `json:"enableSwaggerEndpoint" yaml:"enable_swagger_endpoint"`
AsyncAPI bool `json:"AsyncAPI" yaml:"async_api"` // OBSOLETE
// Database
DatabaseBackend DBConfig `json:"databaseBackend" yaml:"database_backend"`
// Mirroring
Downloader string `json:"downloader" yaml:"downloader"`
DownloadConcurrency int `json:"downloadConcurrency" yaml:"download_concurrency"`
DownloadLimit int64 `json:"downloadSpeedLimit" yaml:"download_limit"`
DownloadRetries int `json:"downloadRetries" yaml:"download_retries"`
DownloadSourcePackages bool `json:"downloadSourcePackages" yaml:"download_sourcepackages"`
// Signing
GpgProvider string `json:"gpgProvider" yaml:"gpg_provider"`
GpgDisableSign bool `json:"gpgDisableSign" yaml:"gpg_disable_sign"`
GpgDisableVerify bool `json:"gpgDisableVerify" yaml:"gpg_disable_verify"`
// Publishing
SkipContentsPublishing bool `json:"skipContentsPublishing" yaml:"skip_contents_publishing"`
SkipBz2Publishing bool `json:"skipBz2Publishing" yaml:"skip_bz2_publishing"`
// Storage
FileSystemPublishRoots map[string]FileSystemPublishRoot `json:"FileSystemPublishEndpoints" yaml:"filesystem_publish_endpoints"`
S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints" yaml:"s3_publish_endpoints"`
SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints" yaml:"swift_publish_endpoints"`
AzurePublishRoots map[string]AzureEndpoint `json:"AzurePublishEndpoints" yaml:"azure_publish_endpoints"`
PackagePoolStorage PackagePoolStorage `json:"packagePoolStorage" yaml:"packagepool_storage"`
}
// DBConfig
type DBConfig struct {
Type string `json:"type"`
URL string `json:"url"`
DbPath string `json:"dbPath"`
Type string `json:"type" yaml:"type"`
DbPath string `json:"dbPath" yaml:"db_path"`
URL string `json:"url" yaml:"url"`
}
type LocalPoolStorage struct {
Path string `json:"path,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
}
type PackagePoolStorage struct {
@@ -61,6 +81,9 @@ type PackagePoolStorage struct {
Azure *AzureEndpoint
}
var AZURE = "azure"
var LOCAL = "local"
func (pool *PackagePoolStorage) UnmarshalJSON(data []byte) error {
var discriminator struct {
Type string `json:"type"`
@@ -71,10 +94,10 @@ func (pool *PackagePoolStorage) UnmarshalJSON(data []byte) error {
}
switch discriminator.Type {
case "azure":
case AZURE:
pool.Azure = &AzureEndpoint{}
return json.Unmarshal(data, &pool.Azure)
case "local", "":
case LOCAL, "":
pool.Local = &LocalPoolStorage{}
return json.Unmarshal(data, &pool.Local)
default:
@@ -82,6 +105,26 @@ func (pool *PackagePoolStorage) UnmarshalJSON(data []byte) error {
}
}
func (pool *PackagePoolStorage) UnmarshalYAML(unmarshal func(interface{}) error) error {
var discriminator struct {
Type string `yaml:"type"`
}
if err := unmarshal(&discriminator); err != nil {
return err
}
switch discriminator.Type {
case AZURE:
pool.Azure = &AzureEndpoint{}
return unmarshal(&pool.Azure)
case LOCAL, "":
pool.Local = &LocalPoolStorage{}
return unmarshal(&pool.Local)
default:
return fmt.Errorf("unknown pool storage type: %s", discriminator.Type)
}
}
func (pool *PackagePoolStorage) MarshalJSON() ([]byte, error) {
var wrapper struct {
Type string `json:"type,omitempty"`
@@ -100,54 +143,72 @@ func (pool *PackagePoolStorage) MarshalJSON() ([]byte, error) {
return json.Marshal(wrapper)
}
func (pool PackagePoolStorage) MarshalYAML() (interface{}, error) {
var wrapper struct {
Type string `yaml:"type,omitempty"`
*LocalPoolStorage `yaml:",inline"`
*AzureEndpoint `yaml:",inline"`
}
if pool.Azure != nil {
wrapper.Type = "azure"
wrapper.AzureEndpoint = pool.Azure
} else if pool.Local.Path != "" {
wrapper.Type = "local"
wrapper.LocalPoolStorage = pool.Local
}
return wrapper, nil
}
// FileSystemPublishRoot describes single filesystem publishing entry point
type FileSystemPublishRoot struct {
RootDir string `json:"rootDir"`
LinkMethod string `json:"linkMethod"`
VerifyMethod string `json:"verifyMethod"`
RootDir string `json:"rootDir" yaml:"root_dir"`
LinkMethod string `json:"linkMethod" yaml:"link_method"`
VerifyMethod string `json:"verifyMethod" yaml:"verify_method"`
}
// S3PublishRoot describes single S3 publishing entry point
type S3PublishRoot struct {
Region string `json:"region"`
Bucket string `json:"bucket"`
Endpoint string `json:"endpoint"`
AccessKeyID string `json:"awsAccessKeyID"`
SecretAccessKey string `json:"awsSecretAccessKey"`
SessionToken string `json:"awsSessionToken"`
Prefix string `json:"prefix"`
ACL string `json:"acl"`
StorageClass string `json:"storageClass"`
EncryptionMethod string `json:"encryptionMethod"`
PlusWorkaround bool `json:"plusWorkaround"`
DisableMultiDel bool `json:"disableMultiDel"`
ForceSigV2 bool `json:"forceSigV2"`
ForceVirtualHostedStyle bool `json:"forceVirtualHostedStyle"`
Debug bool `json:"debug"`
Region string `json:"region" yaml:"region"`
Bucket string `json:"bucket" yaml:"bucket"`
Prefix string `json:"prefix" yaml:"prefix"`
ACL string `json:"acl" yaml:"acl"`
AccessKeyID string `json:"awsAccessKeyID" yaml:"access_key_id"`
SecretAccessKey string `json:"awsSecretAccessKey" yaml:"secret_access_key"`
SessionToken string `json:"awsSessionToken" yaml:"session_token"`
Endpoint string `json:"endpoint" yaml:"endpoint"`
StorageClass string `json:"storageClass" yaml:"storage_class"`
EncryptionMethod string `json:"encryptionMethod" yaml:"encryption_method"`
PlusWorkaround bool `json:"plusWorkaround" yaml:"plus_workaround"`
DisableMultiDel bool `json:"disableMultiDel" yaml:"disable_multidel"`
ForceSigV2 bool `json:"forceSigV2" yaml:"force_sigv2"`
ForceVirtualHostedStyle bool `json:"forceVirtualHostedStyle" yaml:"force_virtualhosted_style"`
Debug bool `json:"debug" yaml:"debug"`
}
// SwiftPublishRoot describes single OpenStack Swift publishing entry point
type SwiftPublishRoot struct {
UserName string `json:"osname"`
Password string `json:"password"`
AuthURL string `json:"authurl"`
Tenant string `json:"tenant"`
TenantID string `json:"tenantid"`
Domain string `json:"domain"`
DomainID string `json:"domainid"`
TenantDomain string `json:"tenantdomain"`
TenantDomainID string `json:"tenantdomainid"`
Prefix string `json:"prefix"`
Container string `json:"container"`
Container string `json:"container" yaml:"container"`
Prefix string `json:"prefix" yaml:"prefix"`
UserName string `json:"osname" yaml:"username"`
Password string `json:"password" yaml:"password"`
Tenant string `json:"tenant" yaml:"tenant"`
TenantID string `json:"tenantid" yaml:"tenant_id"`
Domain string `json:"domain" yaml:"domain"`
DomainID string `json:"domainid" yaml:"domain_id"`
TenantDomain string `json:"tenantdomain" yaml:"tenant_domain"`
TenantDomainID string `json:"tenantdomainid" yaml:"tenant_domain_id"`
AuthURL string `json:"authurl" yaml:"auth_url"`
}
// AzureEndpoint describes single Azure publishing entry point
type AzureEndpoint struct {
AccountName string `json:"accountName"`
AccountKey string `json:"accountKey"`
Container string `json:"container"`
Prefix string `json:"prefix"`
Endpoint string `json:"endpoint"`
Container string `json:"container" yaml:"container"`
Prefix string `json:"prefix" yaml:"prefix"`
AccountName string `json:"accountName" yaml:"account_name"`
AccountKey string `json:"accountKey" yaml:"account_key"`
Endpoint string `json:"endpoint" yaml:"endpoint"`
}
// Config is configuration for aptly, shared by all modules
@@ -178,7 +239,7 @@ var Config = ConfigStructure{
AzurePublishRoots: map[string]AzureEndpoint{},
AsyncAPI: false,
EnableMetricsEndpoint: false,
LogLevel: "debug",
LogLevel: "info",
LogFormat: "default",
ServeInAPIMode: false,
EnableSwaggerEndpoint: false,
@@ -192,8 +253,17 @@ func LoadConfig(filename string, config *ConfigStructure) error {
}
defer f.Close()
dec := json.NewDecoder(f)
return dec.Decode(&config)
decJSON := json.NewDecoder(JsonConfigReader.New(f))
if err = decJSON.Decode(&config); err != nil {
f.Seek(0, 0)
decYAML := yaml.NewDecoder(f)
if err2 := decYAML.Decode(&config); err2 != nil {
err = fmt.Errorf("invalid yaml (%s) or json (%s)", err2, err)
} else {
err = nil
}
}
return err
}
// SaveConfig write configuration to json file
@@ -213,6 +283,35 @@ func SaveConfig(filename string, config *ConfigStructure) error {
return err
}
// SaveConfigRaw write configuration to file
func SaveConfigRaw(filename string, conf []byte) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(conf)
return err
}
// SaveConfigYAML write configuration to yaml file
func SaveConfigYAML(filename string, config *ConfigStructure) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
yamlData, err := yaml.Marshal(&config)
if err != nil {
return fmt.Errorf("error marshaling to YAML: %s", err)
}
_, err = f.Write(yamlData)
return err
}
// GetRootDir returns the RootDir with expanded ~ as home directory
func (conf *ConfigStructure) GetRootDir() string {
return strings.Replace(conf.RootDir, "~", os.Getenv("HOME"), 1)
+306 -89
View File
@@ -14,11 +14,14 @@ type ConfigSuite struct {
var _ = Suite(&ConfigSuite{})
func (s *ConfigSuite) TestLoadConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.json")
configname := filepath.Join(c.MkDir(), "aptly.json1")
f, _ := os.Create(configname)
f.WriteString(configFile)
f.Close()
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err, IsNil)
c.Check(s.config.GetRootDir(), Equals, "/opt/aptly/")
@@ -27,7 +30,10 @@ func (s *ConfigSuite) TestLoadConfig(c *C) {
}
func (s *ConfigSuite) TestSaveConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.json")
configname := filepath.Join(c.MkDir(), "aptly.json2")
// start with empty config
s.config = ConfigStructure{}
s.config.RootDir = "/tmp/aptly"
s.config.DownloadConcurrency = 5
@@ -63,94 +69,305 @@ func (s *ConfigSuite) TestSaveConfig(c *C) {
f.Read(buf)
c.Check(string(buf), Equals, ""+
"{\n"+
" \"rootDir\": \"/tmp/aptly\",\n"+
" \"downloadConcurrency\": 5,\n"+
" \"downloadSpeedLimit\": 0,\n"+
" \"downloadRetries\": 0,\n"+
" \"downloader\": \"\",\n"+
" \"databaseOpenAttempts\": 5,\n"+
" \"architectures\": null,\n"+
" \"dependencyFollowSuggests\": false,\n"+
" \"dependencyFollowRecommends\": false,\n"+
" \"dependencyFollowAllVariants\": false,\n"+
" \"dependencyFollowSource\": false,\n"+
" \"dependencyVerboseResolve\": false,\n"+
" \"gpgDisableSign\": false,\n"+
" \"gpgDisableVerify\": false,\n"+
" \"gpgProvider\": \"gpg\",\n"+
" \"downloadSourcePackages\": false,\n"+
" \"packagePoolStorage\": {\n"+
" \"type\": \"local\",\n"+
" \"path\": \"/tmp/aptly-pool\"\n"+
" },\n"+
" \"skipLegacyPool\": false,\n"+
" \"ppaDistributorID\": \"\",\n"+
" \"ppaCodename\": \"\",\n"+
" \"skipContentsPublishing\": false,\n"+
" \"skipBz2Publishing\": false,\n"+
" \"FileSystemPublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"rootDir\": \"/opt/aptly-publish\",\n"+
" \"linkMethod\": \"\",\n"+
" \"verifyMethod\": \"\"\n"+
" }\n"+
" },\n"+
" \"S3PublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"region\": \"us-east-1\",\n"+
" \"bucket\": \"repo\",\n"+
" \"endpoint\": \"\",\n"+
" \"awsAccessKeyID\": \"\",\n"+
" \"awsSecretAccessKey\": \"\",\n"+
" \"awsSessionToken\": \"\",\n"+
" \"prefix\": \"\",\n"+
" \"acl\": \"\",\n"+
" \"storageClass\": \"\",\n"+
" \"encryptionMethod\": \"\",\n"+
" \"plusWorkaround\": false,\n"+
" \"disableMultiDel\": false,\n"+
" \"forceSigV2\": false,\n"+
" \"forceVirtualHostedStyle\": false,\n"+
" \"debug\": false\n"+
" }\n"+
" },\n"+
" \"SwiftPublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"osname\": \"\",\n"+
" \"password\": \"\",\n"+
" \"authurl\": \"\",\n"+
" \"tenant\": \"\",\n"+
" \"tenantid\": \"\",\n"+
" \"domain\": \"\",\n"+
" \"domainid\": \"\",\n"+
" \"tenantdomain\": \"\",\n"+
" \"tenantdomainid\": \"\",\n"+
" \"prefix\": \"\",\n"+
" \"container\": \"repo\"\n"+
" }\n"+
" },\n"+
" \"AzurePublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"accountName\": \"\",\n"+
" \"accountKey\": \"\",\n"+
" \"container\": \"repo\",\n"+
" \"prefix\": \"\",\n"+
" \"endpoint\": \"\"\n"+
" }\n"+
" },\n"+
" \"AsyncAPI\": false,\n"+
" \"enableMetricsEndpoint\": false,\n"+
" \"logLevel\": \"info\",\n"+
" \"logFormat\": \"json\",\n"+
" \"serveInAPIMode\": false,\n"+
" \"databaseBackend\": {\n"+
" \"type\": \"\",\n"+
" \"url\": \"\",\n"+
" \"dbPath\": \"\"\n" +
" },\n"+
" \"enableSwaggerEndpoint\": false\n" +
"{\n" +
" \"rootDir\": \"/tmp/aptly\",\n" +
" \"logLevel\": \"info\",\n" +
" \"logFormat\": \"json\",\n" +
" \"databaseOpenAttempts\": 5,\n" +
" \"architectures\": null,\n" +
" \"skipLegacyPool\": false,\n" +
" \"dependencyFollowSuggests\": false,\n" +
" \"dependencyFollowRecommends\": false,\n" +
" \"dependencyFollowAllVariants\": false,\n" +
" \"dependencyFollowSource\": false,\n" +
" \"dependencyVerboseResolve\": false,\n" +
" \"ppaDistributorID\": \"\",\n" +
" \"ppaCodename\": \"\",\n" +
" \"serveInAPIMode\": false,\n" +
" \"enableMetricsEndpoint\": false,\n" +
" \"enableSwaggerEndpoint\": false,\n" +
" \"AsyncAPI\": false,\n" +
" \"databaseBackend\": {\n" +
" \"type\": \"\",\n" +
" \"dbPath\": \"\",\n" +
" \"url\": \"\"\n" +
" },\n" +
" \"downloader\": \"\",\n" +
" \"downloadConcurrency\": 5,\n" +
" \"downloadSpeedLimit\": 0,\n" +
" \"downloadRetries\": 0,\n" +
" \"downloadSourcePackages\": false,\n" +
" \"gpgProvider\": \"gpg\",\n" +
" \"gpgDisableSign\": false,\n" +
" \"gpgDisableVerify\": false,\n" +
" \"skipContentsPublishing\": false,\n" +
" \"skipBz2Publishing\": false,\n" +
" \"FileSystemPublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"rootDir\": \"/opt/aptly-publish\",\n" +
" \"linkMethod\": \"\",\n" +
" \"verifyMethod\": \"\"\n" +
" }\n" +
" },\n" +
" \"S3PublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"region\": \"us-east-1\",\n" +
" \"bucket\": \"repo\",\n" +
" \"prefix\": \"\",\n" +
" \"acl\": \"\",\n" +
" \"awsAccessKeyID\": \"\",\n" +
" \"awsSecretAccessKey\": \"\",\n" +
" \"awsSessionToken\": \"\",\n" +
" \"endpoint\": \"\",\n" +
" \"storageClass\": \"\",\n" +
" \"encryptionMethod\": \"\",\n" +
" \"plusWorkaround\": false,\n" +
" \"disableMultiDel\": false,\n" +
" \"forceSigV2\": false,\n" +
" \"forceVirtualHostedStyle\": false,\n" +
" \"debug\": false\n" +
" }\n" +
" },\n" +
" \"SwiftPublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"container\": \"repo\",\n" +
" \"prefix\": \"\",\n" +
" \"osname\": \"\",\n" +
" \"password\": \"\",\n" +
" \"tenant\": \"\",\n" +
" \"tenantid\": \"\",\n" +
" \"domain\": \"\",\n" +
" \"domainid\": \"\",\n" +
" \"tenantdomain\": \"\",\n" +
" \"tenantdomainid\": \"\",\n" +
" \"authurl\": \"\"\n" +
" }\n" +
" },\n" +
" \"AzurePublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"container\": \"repo\",\n" +
" \"prefix\": \"\",\n" +
" \"accountName\": \"\",\n" +
" \"accountKey\": \"\",\n" +
" \"endpoint\": \"\"\n" +
" }\n" +
" },\n" +
" \"packagePoolStorage\": {\n" +
" \"type\": \"local\",\n" +
" \"path\": \"/tmp/aptly-pool\"\n" +
" }\n" +
"}")
}
func (s *ConfigSuite) TestLoadYAMLConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.yaml1")
f, _ := os.Create(configname)
f.WriteString(configFileYAML)
f.Close()
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err, IsNil)
c.Check(s.config.GetRootDir(), Equals, "/opt/aptly/")
c.Check(s.config.DownloadConcurrency, Equals, 40)
c.Check(s.config.DatabaseOpenAttempts, Equals, 10)
}
func (s *ConfigSuite) TestLoadYAMLErrorConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.yaml2")
f, _ := os.Create(configname)
f.WriteString(configFileYAMLError)
f.Close()
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err.Error(), Equals, "invalid yaml (unknown pool storage type: invalid) or json (invalid character 'p' looking for beginning of value)")
}
func (s *ConfigSuite) TestSaveYAMLConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.yaml3")
f, _ := os.Create(configname)
f.WriteString(configFileYAML)
f.Close()
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err, IsNil)
err = SaveConfigYAML(configname, &s.config)
c.Assert(err, IsNil)
f, _ = os.Open(configname)
defer f.Close()
st, _ := f.Stat()
buf := make([]byte, st.Size())
f.Read(buf)
c.Check(string(buf), Equals, configFileYAML)
}
func (s *ConfigSuite) TestSaveYAML2Config(c *C) {
// start with empty config
s.config = ConfigStructure{}
s.config.PackagePoolStorage.Local = &LocalPoolStorage{"/tmp/aptly-pool"}
s.config.PackagePoolStorage.Azure = nil
configname := filepath.Join(c.MkDir(), "aptly.yaml4")
err := SaveConfigYAML(configname, &s.config)
c.Assert(err, IsNil)
f, _ := os.Open(configname)
defer f.Close()
st, _ := f.Stat()
buf := make([]byte, st.Size())
f.Read(buf)
c.Check(string(buf), Equals, "" +
"root_dir: \"\"\n" +
"log_level: \"\"\n" +
"log_format: \"\"\n" +
"database_open_attempts: 0\n" +
"architectures: []\n" +
"skip_legacy_pool: false\n" +
"dep_follow_suggests: false\n" +
"dep_follow_recommends: false\n" +
"dep_follow_all_variants: false\n" +
"dep_follow_source: false\n" +
"dep_verboseresolve: false\n" +
"ppa_distributor_id: \"\"\n" +
"ppa_codename: \"\"\n" +
"serve_in_api_mode: false\n" +
"enable_metrics_endpoint: false\n" +
"enable_swagger_endpoint: false\n" +
"async_api: false\n" +
"database_backend:\n" +
" type: \"\"\n" +
" db_path: \"\"\n" +
" url: \"\"\n" +
"downloader: \"\"\n" +
"download_concurrency: 0\n" +
"download_limit: 0\n" +
"download_retries: 0\n" +
"download_sourcepackages: false\n" +
"gpg_provider: \"\"\n" +
"gpg_disable_sign: false\n" +
"gpg_disable_verify: false\n" +
"skip_contents_publishing: false\n" +
"skip_bz2_publishing: false\n" +
"filesystem_publish_endpoints: {}\n" +
"s3_publish_endpoints: {}\n" +
"swift_publish_endpoints: {}\n" +
"azure_publish_endpoints: {}\n" +
"packagepool_storage:\n" +
" type: local\n" +
" path: /tmp/aptly-pool\n")
}
func (s *ConfigSuite) TestLoadEmptyConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.yaml5")
f, _ := os.Create(configname)
f.Close()
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err.Error(), Equals, "invalid yaml (EOF) or json (EOF)")
}
const configFile = `{"rootDir": "/opt/aptly/", "downloadConcurrency": 33, "databaseOpenAttempts": 33}`
const configFileYAML = `root_dir: /opt/aptly/
log_level: error
log_format: json
database_open_attempts: 10
architectures:
- amd64
- arm64
skip_legacy_pool: true
dep_follow_suggests: true
dep_follow_recommends: true
dep_follow_all_variants: true
dep_follow_source: true
dep_verboseresolve: true
ppa_distributor_id: Ubuntu
ppa_codename: code
serve_in_api_mode: true
enable_metrics_endpoint: true
enable_swagger_endpoint: true
async_api: true
database_backend:
type: etcd
db_path: ""
url: 127.0.0.1:2379
downloader: grab
download_concurrency: 40
download_limit: 100
download_retries: 10
download_sourcepackages: true
gpg_provider: gpg
gpg_disable_sign: true
gpg_disable_verify: true
skip_contents_publishing: true
skip_bz2_publishing: true
filesystem_publish_endpoints:
test1:
root_dir: /opt/srv/aptly_public
link_method: hardlink
verify_method: md5
s3_publish_endpoints:
test:
region: us-east-1
bucket: test-bucket
prefix: prfx
acl: public-read
access_key_id: "2"
secret_access_key: secret
session_token: none
endpoint: endpoint
storage_class: STANDARD
encryption_method: AES256
plus_workaround: true
disable_multidel: true
force_sigv2: true
force_virtualhosted_style: true
debug: true
swift_publish_endpoints:
test:
container: c1
prefix: pre
username: user
password: pass
tenant: t
tenant_id: "2"
domain: pop
domain_id: "1"
tenant_domain: td
tenant_domain_id: "3"
auth_url: http://auth.url
azure_publish_endpoints:
test:
container: container1
prefix: pre2
account_name: aname
account_key: akey
endpoint: https://end.point
packagepool_storage:
type: azure
container: test-pool1
prefix: pre3
account_name: a name
account_key: a key
endpoint: ep
`
const configFileYAMLError = `packagepool_storage:
type: invalid
`