mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-01-11 03:11:50 +00:00
Merge branch 'master' of https://github.com/smira/aptly
This commit is contained in:
14
.github/ISSUE_TEMPLATE.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Detailed Description
|
||||
<!--- Provide a detailed description of the change or addition you are proposing -->
|
||||
|
||||
## Context
|
||||
<!--- Why is this change important to you? How would you use it? -->
|
||||
<!--- How can it benefit other users? -->
|
||||
|
||||
## Possible Implementation
|
||||
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
Fixes #
|
||||
|
||||
## Requirements
|
||||
|
||||
All new code should be covered with tests, documentation should be updated. CI should pass.
|
||||
|
||||
## Description of the Change
|
||||
|
||||
<!--
|
||||
|
||||
Why this change is important?
|
||||
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] unit-test added (if change is algorithm)
|
||||
- [ ] functional test added/updated (if change is functional)
|
||||
- [ ] man page updated (if applicable)
|
||||
- [ ] documentation updated
|
||||
- [ ] author name in `AUTHORS`
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -27,8 +27,12 @@ coverage*.out
|
||||
|
||||
*.pyc
|
||||
|
||||
_vendor/
|
||||
vendor/
|
||||
xc-out/
|
||||
root/
|
||||
|
||||
gen
|
||||
man/aptly.1.html
|
||||
man/aptly.1.ronn
|
||||
man/aptly.1.ronn
|
||||
|
||||
.goxc.local.json
|
||||
|
||||
10
.goxc.json
10
.goxc.json
@@ -2,10 +2,12 @@
|
||||
"AppName": "aptly",
|
||||
"ArtifactsDest": "xc-out/",
|
||||
"TasksExclude": [
|
||||
"rmbin"
|
||||
"rmbin",
|
||||
"go-test",
|
||||
"go-vet"
|
||||
],
|
||||
"TasksAppend": [
|
||||
"bintray"
|
||||
"bintray"
|
||||
],
|
||||
"TaskSettings": {
|
||||
"deb": {
|
||||
@@ -33,6 +35,6 @@
|
||||
},
|
||||
"Arch": "386 amd64",
|
||||
"Os": "linux darwin freebsd",
|
||||
"MainDirsExclude": "man,_vendor",
|
||||
"MainDirsExclude": "man,vendor",
|
||||
"ConfigVersion": "0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- tip
|
||||
|
||||
addons:
|
||||
@@ -21,7 +21,12 @@ env:
|
||||
before_install:
|
||||
- virtualenv env
|
||||
- . env/bin/activate
|
||||
- pip install six packaging appdirs
|
||||
- pip install -U pip setuptools
|
||||
- pip install boto requests python-swiftclient
|
||||
- mkdir -p $GOPATH/src/github.com/smira
|
||||
- ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/smira || true
|
||||
- cd $GOPATH/src/github.com/smira/aptly
|
||||
install:
|
||||
- make prepare
|
||||
|
||||
|
||||
3
AUTHORS
3
AUTHORS
@@ -22,4 +22,7 @@ List of contributors, in chronological order:
|
||||
* Phil Frost (https://github.com/bitglue)
|
||||
* Benoit Foucher (https://github.com/bentoi)
|
||||
* Geoffrey Thomas (https://github.com/geofft)
|
||||
* Oliver Sauder (https://github.com/sliverc)
|
||||
* Harald Sitter (https://github.com/apachelogger)
|
||||
* Johannes Layher (https://github.com/jola5)
|
||||
* Charles Hsu (https://github.com/charz)
|
||||
|
||||
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [team@aptly.info](mailto:team@aptly.info). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
2
Gomfile
2
Gomfile
@@ -5,6 +5,7 @@ gom 'github.com/cheggaaa/pb', :commit => '2c1b74620cc58a81ac152ee2d322e28c806d81
|
||||
gom 'github.com/DisposaBoy/JsonConfigReader', :commit => '33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4'
|
||||
gom 'github.com/gin-gonic/gin', :commit => 'b1758d3bfa09e61ddbc1c9a627e936eec6a170de'
|
||||
gom 'github.com/go-ini/ini', :commit => 'afbd495e5aaea13597b5e14fe514ddeaa4d76fc3'
|
||||
gom 'github.com/h2non/filetype/matchers', :commit => '259d9d2c52bc90dbd7e1999f9da86ee0d104d0ff'
|
||||
gom 'github.com/jlaffaye/ftp', :commit => 'fec71e62e457557fbe85cefc847a048d57815d76'
|
||||
gom 'github.com/jmespath/go-jmespath', :commit => '0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74'
|
||||
gom 'github.com/julienschmidt/httprouter', :commit => '46807412fe50aaceb73bb57061c2230fd26a1640'
|
||||
@@ -25,6 +26,7 @@ gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece58101
|
||||
gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1'
|
||||
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
|
||||
gom 'golang.org/x/crypto/ssh/terminal', :commit => 'a7ead6ddf06233883deca151dffaef2effbf498f'
|
||||
gom 'golang.org/x/sys/unix', :commit => '7a6e5648d140666db5d920909e082ca00a87ba2c'
|
||||
|
||||
group :test do
|
||||
gom 'gopkg.in/check.v1'
|
||||
|
||||
12
Makefile
12
Makefile
@@ -1,9 +1,10 @@
|
||||
GOVERSION=$(shell go version | awk '{print $$3;}')
|
||||
PACKAGES=context database deb files http query swift s3 utils
|
||||
ALL_PACKAGES=api aptly context cmd console database deb files http query swift s3 utils
|
||||
BINPATH=$(abspath ./_vendor/bin)
|
||||
BINPATH=$(abspath ./vendor/bin)
|
||||
GOM_ENVIRONMENT=-test
|
||||
PYTHON?=python
|
||||
TESTS?=
|
||||
|
||||
ifeq ($(GOVERSION), devel)
|
||||
TRAVIS_TARGET=coveralls
|
||||
@@ -45,7 +46,7 @@ install:
|
||||
system-test: install
|
||||
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
|
||||
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
|
||||
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long
|
||||
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long $(TESTS)
|
||||
|
||||
travis: $(TRAVIS_TARGET) system-test
|
||||
|
||||
@@ -65,7 +66,7 @@ src-package:
|
||||
cd aptly-$(VERSION)/src/github.com/smira/ && git clone https://github.com/smira/aptly && cd aptly && git checkout v$(VERSION)
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf
|
||||
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin}
|
||||
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/vendor/{pkg,bin}
|
||||
mkdir -p aptly-$(VERSION)/bash_completion.d
|
||||
(cd aptly-$(VERSION)/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/$(VERSION)/aptly)
|
||||
tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
|
||||
@@ -80,4 +81,7 @@ goxc:
|
||||
gzip root/usr/share/man/man1/aptly.1
|
||||
gom exec goxc -pv=$(VERSION) -max-processors=4 $(GOXC_OPTS)
|
||||
|
||||
.PHONY: coverage.out
|
||||
man:
|
||||
make -C man
|
||||
|
||||
.PHONY: coverage.out man
|
||||
|
||||
@@ -64,7 +64,7 @@ If you would like to use nightly builds (unstable), please use following reposit
|
||||
|
||||
Binary executables (depends almost only on libc) are available for download from `Bintray <http://dl.bintray.com/smira/aptly/>`_.
|
||||
|
||||
If you have Go environment set up, you can build aptly from source by running (go 1.4+ required)::
|
||||
If you have Go environment set up, you can build aptly from source by running (go 1.6+ required)::
|
||||
|
||||
go get -u github.com/mattn/gom
|
||||
mkdir -p $GOPATH/src/github.com/smira/aptly
|
||||
@@ -107,6 +107,7 @@ With configuration management systems:
|
||||
CLI for aptly API:
|
||||
|
||||
- `Ruby aptly CLI/library <https://github.com/sepulworld/aptly_cli>`_ by Zane Williamson
|
||||
- `Python aptly CLI (good for CI) <https://github.com/TimSusa/aptly_api_cli>`_ by Tim Susa
|
||||
|
||||
Scala sbt:
|
||||
|
||||
|
||||
12
api/graph.go
12
api/graph.go
@@ -11,7 +11,7 @@ import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// GET /api/graph.:ext
|
||||
// GET /api/graph.:ext?layout=[vertical|horizontal(default)]
|
||||
func apiGraph(c *gin.Context) {
|
||||
var (
|
||||
err error
|
||||
@@ -19,6 +19,7 @@ func apiGraph(c *gin.Context) {
|
||||
)
|
||||
|
||||
ext := c.Params.ByName("ext")
|
||||
layout := c.Request.URL.Query().Get("layout")
|
||||
|
||||
factory := context.CollectionFactory()
|
||||
|
||||
@@ -31,7 +32,7 @@ func apiGraph(c *gin.Context) {
|
||||
factory.PublishedRepoCollection().RLock()
|
||||
defer factory.PublishedRepoCollection().RUnlock()
|
||||
|
||||
graph, err := deb.BuildGraph(factory)
|
||||
graph, err := deb.BuildGraph(factory, layout)
|
||||
if err != nil {
|
||||
c.JSON(500, err)
|
||||
return
|
||||
@@ -39,6 +40,13 @@ func apiGraph(c *gin.Context) {
|
||||
|
||||
buf := bytes.NewBufferString(graph.String())
|
||||
|
||||
if ext == "dot" || ext == "gv" {
|
||||
// If the raw dot data is requested, return it as string.
|
||||
// This allows client-side rendering rather than server-side.
|
||||
c.String(200, buf.String())
|
||||
return
|
||||
}
|
||||
|
||||
command := exec.Command("dot", "-T"+ext)
|
||||
command.Stderr = os.Stderr
|
||||
|
||||
|
||||
18
api/repos.go
18
api/repos.go
@@ -60,9 +60,9 @@ func apiReposCreate(c *gin.Context) {
|
||||
// PUT /api/repos/:name
|
||||
func apiReposEdit(c *gin.Context) {
|
||||
var b struct {
|
||||
Comment string
|
||||
DefaultDistribution string
|
||||
DefaultComponent string
|
||||
Comment *string
|
||||
DefaultDistribution *string
|
||||
DefaultComponent *string
|
||||
}
|
||||
|
||||
if !c.Bind(&b) {
|
||||
@@ -79,14 +79,14 @@ func apiReposEdit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if b.Comment != "" {
|
||||
repo.Comment = b.Comment
|
||||
if b.Comment != nil {
|
||||
repo.Comment = *b.Comment
|
||||
}
|
||||
if b.DefaultDistribution != "" {
|
||||
repo.DefaultDistribution = b.DefaultDistribution
|
||||
if b.DefaultDistribution != nil {
|
||||
repo.DefaultDistribution = *b.DefaultDistribution
|
||||
}
|
||||
if b.DefaultComponent != "" {
|
||||
repo.DefaultComponent = b.DefaultComponent
|
||||
if b.DefaultComponent != nil {
|
||||
repo.DefaultComponent = *b.DefaultComponent
|
||||
}
|
||||
|
||||
err = collection.Update(repo)
|
||||
|
||||
@@ -82,7 +82,7 @@ type Downloader interface {
|
||||
// Download starts new download task
|
||||
Download(url string, destination string, result chan<- error)
|
||||
// DownloadWithChecksum starts new download task with checksum verification
|
||||
DownloadWithChecksum(url string, destination string, result chan<- error, expected utils.ChecksumInfo, ignoreMismatch bool)
|
||||
DownloadWithChecksum(url string, destination string, result chan<- error, expected utils.ChecksumInfo, ignoreMismatch bool, maxTries int)
|
||||
// Pause pauses task processing
|
||||
Pause()
|
||||
// Resume resumes task processing
|
||||
|
||||
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/api"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"net/http"
|
||||
@@ -18,6 +19,17 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
// There are only two working options for aptly's rootDir:
|
||||
// 1. rootDir does not exist, then we'll create it
|
||||
// 2. rootDir exists and is writable
|
||||
// anything else must fail.
|
||||
// E.g.: Running the service under a different user may lead to a rootDir
|
||||
// that exists but is not usable due to access permissions.
|
||||
err = utils.DirIsAccessible(context.Config().RootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listen := context.Flags().Lookup("listen").Value.String()
|
||||
|
||||
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
|
||||
|
||||
@@ -21,8 +21,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
layout := context.Flags().Lookup("layout").Value.String()
|
||||
|
||||
fmt.Printf("Generating graph...\n")
|
||||
graph, err := deb.BuildGraph(context.CollectionFactory())
|
||||
graph, err := deb.BuildGraph(context.CollectionFactory(), layout)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -108,6 +111,7 @@ Example:
|
||||
|
||||
cmd.Flag.String("format", "png", "render graph to specified format (png, svg, pdf, etc.)")
|
||||
cmd.Flag.String("output", "", "specify output filename, default is to open result in viewer")
|
||||
cmd.Flag.String("layout", "horizontal", "create a more 'vertical' or a more 'horizontal' graph layout")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
func makeCmdMirrorSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMirrorRepoSearch,
|
||||
UsageLine: "search <name> <package-query>",
|
||||
UsageLine: "search <name> [<package-query>]",
|
||||
Short: "search mirror for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in mirror that match package query
|
||||
|
||||
If query is not specified, all the packages are displayed.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror search wheezy-main '$Architecture (i386), Name (% *-dev)'
|
||||
|
||||
@@ -40,6 +40,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
|
||||
maxTries := context.Flags().Lookup("max-tries").Value.Get().(int)
|
||||
|
||||
verifier, err := getVerifier(context.Flags())
|
||||
if err != nil {
|
||||
@@ -52,7 +53,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
context.Progress().Printf("Downloading & parsing package files...\n")
|
||||
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), context.CollectionFactory(), ignoreMismatch)
|
||||
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), context.CollectionFactory(), ignoreMismatch, maxTries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -121,7 +122,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
// In separate goroutine (to avoid blocking main), push queue to downloader
|
||||
go func() {
|
||||
for _, task := range queue {
|
||||
context.Downloader().DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch)
|
||||
context.Downloader().DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch, maxTries)
|
||||
}
|
||||
|
||||
// We don't need queue after this point
|
||||
@@ -187,6 +188,7 @@ Example:
|
||||
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
||||
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -2,21 +2,31 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyPackageSearch(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
var (
|
||||
err error
|
||||
q deb.PackageQuery
|
||||
)
|
||||
|
||||
if len(args) > 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
q, err := query.Parse(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
if len(args) == 1 {
|
||||
q, err = query.Parse(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
} else {
|
||||
q = &deb.MatchAllQuery{}
|
||||
}
|
||||
|
||||
result := q.Query(context.CollectionFactory().PackageCollection())
|
||||
@@ -33,10 +43,12 @@ func aptlyPackageSearch(cmd *commander.Command, args []string) error {
|
||||
func makeCmdPackageSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPackageSearch,
|
||||
UsageLine: "search <package-query>",
|
||||
UsageLine: "search [<package-query>]",
|
||||
Short: "search for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in whole DB that match package query
|
||||
Command search displays list of packages in whole DB that match package query.
|
||||
|
||||
If query is not specified, all the packages are displayed.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ func makeCmdPublish() *commander.Command {
|
||||
makeCmdPublishSnapshot(),
|
||||
makeCmdPublishSwitch(),
|
||||
makeCmdPublishUpdate(),
|
||||
makeCmdPublishShow(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
80
cmd/publish_show.go
Normal file
80
cmd/publish_show.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyPublishShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
param := "."
|
||||
|
||||
if len(args) == 2 {
|
||||
param = args[1]
|
||||
}
|
||||
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
repo, err := context.CollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
if repo.Storage != "" {
|
||||
fmt.Printf("Storage: %s\n", repo.Storage)
|
||||
}
|
||||
fmt.Printf("Prefix: %s\n", repo.Prefix)
|
||||
if repo.Distribution != "" {
|
||||
fmt.Printf("Distribution: %s\n", repo.Distribution)
|
||||
}
|
||||
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, " "))
|
||||
|
||||
fmt.Printf("Sources:\n")
|
||||
for component, sourceID := range repo.Sources {
|
||||
var name string
|
||||
if repo.SourceKind == "snapshot" {
|
||||
source, err := context.CollectionFactory().SnapshotCollection().ByUUID(sourceID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name = source.Name
|
||||
} else if repo.SourceKind == "local" {
|
||||
source, err := context.CollectionFactory().LocalRepoCollection().ByUUID(sourceID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name = source.Name
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
fmt.Printf(" %s: %s [%s]\n", component, name, repo.SourceKind)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishShow,
|
||||
UsageLine: "show <distribution> [[<endpoint>:]<prefix>]",
|
||||
Short: "shows details of published repository",
|
||||
Long: `
|
||||
Command show displays full information of a published repository.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish show wheezy
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
func makeCmdRepoSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMirrorRepoSearch,
|
||||
UsageLine: "search <name> <package-query>",
|
||||
UsageLine: "search <name> [<package-query>]",
|
||||
Short: "search repo for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in local repository that match package query
|
||||
|
||||
If query is not specified, all the packages are displayed.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo search my-software '$Architecture (i386), Name (% *-dev)'
|
||||
|
||||
11
cmd/serve.go
11
cmd/serve.go
@@ -22,6 +22,17 @@ func aptlyServe(cmd *commander.Command, args []string) error {
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
// There are only two working options for aptly's rootDir:
|
||||
// 1. rootDir does not exist, then we'll create it
|
||||
// 2. rootDir exists and is writable
|
||||
// anything else must fail.
|
||||
// E.g.: Running the service under a different user may lead to a rootDir
|
||||
// that exists but is not usable due to access permissions.
|
||||
err = utils.DirIsAccessible(context.Config().RootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if context.CollectionFactory().PublishedRepoCollection().Len() == 0 {
|
||||
fmt.Printf("No published repositories, unable to serve.\n")
|
||||
return nil
|
||||
|
||||
@@ -2,16 +2,21 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 2 {
|
||||
var (
|
||||
err error
|
||||
q deb.PackageQuery
|
||||
)
|
||||
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
@@ -68,9 +73,13 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
|
||||
|
||||
list.PrepareIndex()
|
||||
|
||||
q, err := query.Parse(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
if len(args) == 2 {
|
||||
q, err = query.Parse(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
} else {
|
||||
q = &deb.MatchAllQuery{}
|
||||
}
|
||||
|
||||
withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
|
||||
@@ -109,11 +118,13 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
|
||||
func makeCmdSnapshotSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMirrorRepoSearch,
|
||||
UsageLine: "search <name> <package-query>",
|
||||
UsageLine: "search <name> [<package-query>]",
|
||||
Short: "search snapshot for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in snapshot that match package query
|
||||
|
||||
If query is not specified, all the packages are displayed.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot search wheezy-main '$Architecture (i386), Name (% *-dev)'
|
||||
|
||||
@@ -29,6 +29,35 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error {
|
||||
fmt.Printf("Created At: %s\n", snapshot.CreatedAt.Format("2006-01-02 15:04:05 MST"))
|
||||
fmt.Printf("Description: %s\n", snapshot.Description)
|
||||
fmt.Printf("Number of packages: %d\n", snapshot.NumPackages())
|
||||
if len(snapshot.SourceIDs) > 0 {
|
||||
fmt.Printf("Sources:\n")
|
||||
for _, sourceID := range snapshot.SourceIDs {
|
||||
var name string
|
||||
if snapshot.SourceKind == "snapshot" {
|
||||
source, err := context.CollectionFactory().SnapshotCollection().ByUUID(sourceID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name = source.Name
|
||||
} else if snapshot.SourceKind == "local" {
|
||||
source, err := context.CollectionFactory().LocalRepoCollection().ByUUID(sourceID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name = source.Name
|
||||
} else if snapshot.SourceKind == "repo" {
|
||||
source, err := context.CollectionFactory().RemoteRepoCollection().ByUUID(sourceID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name = source.Name
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
fmt.Printf(" %s [%s]\n", name, snapshot.SourceKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
|
||||
if withPackages {
|
||||
|
||||
42
deb/deb.go
42
deb/deb.go
@@ -2,16 +2,20 @@ package deb
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"github.com/mkrautz/goar"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/go-xz"
|
||||
"github.com/smira/lzma"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/h2non/filetype/matchers"
|
||||
"github.com/mkrautz/goar"
|
||||
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/go-xz"
|
||||
"github.com/smira/lzma"
|
||||
)
|
||||
|
||||
// GetControlFileFromDeb reads control file from deb package
|
||||
@@ -119,29 +123,41 @@ func GetContentsFromDeb(packageFile string) ([]string, error) {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(header.Name, "data.tar") {
|
||||
bufReader := bufio.NewReader(library)
|
||||
signature, err := bufReader.Peek(270)
|
||||
|
||||
var isTar bool
|
||||
if err == nil {
|
||||
isTar = matchers.Tar(signature)
|
||||
}
|
||||
|
||||
var tarInput io.Reader
|
||||
|
||||
switch header.Name {
|
||||
case "data.tar":
|
||||
tarInput = library
|
||||
tarInput = bufReader
|
||||
case "data.tar.gz":
|
||||
ungzip, err := gzip.NewReader(library)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ungzip data.tar.gz from %s: %s", packageFile, err)
|
||||
if isTar {
|
||||
tarInput = bufReader
|
||||
} else {
|
||||
ungzip, err := gzip.NewReader(bufReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ungzip data.tar.gz from %s: %s", packageFile, err)
|
||||
}
|
||||
defer ungzip.Close()
|
||||
tarInput = ungzip
|
||||
}
|
||||
defer ungzip.Close()
|
||||
tarInput = ungzip
|
||||
case "data.tar.bz2":
|
||||
tarInput = bzip2.NewReader(library)
|
||||
tarInput = bzip2.NewReader(bufReader)
|
||||
case "data.tar.xz":
|
||||
unxz, err := xz.NewReader(library)
|
||||
unxz, err := xz.NewReader(bufReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unxz data.tar.xz from %s: %s", packageFile, err)
|
||||
}
|
||||
defer unxz.Close()
|
||||
tarInput = unxz
|
||||
case "data.tar.lzma":
|
||||
unlzma := lzma.NewReader(library)
|
||||
unlzma := lzma.NewReader(bufReader)
|
||||
defer unlzma.Close()
|
||||
tarInput = unlzma
|
||||
default:
|
||||
|
||||
35
deb/graph.go
35
deb/graph.go
@@ -7,13 +7,28 @@ import (
|
||||
)
|
||||
|
||||
// BuildGraph generates graph contents from aptly object database
|
||||
func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, error) {
|
||||
func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz.Interface, error) {
|
||||
var err error
|
||||
|
||||
graph := gographviz.NewEscape()
|
||||
graph.SetDir(true)
|
||||
graph.SetName("aptly")
|
||||
|
||||
var labelStart string
|
||||
var labelEnd string
|
||||
|
||||
switch layout {
|
||||
case "vertical":
|
||||
graph.AddAttr("aptly", "rankdir", "LR")
|
||||
labelStart = ""
|
||||
labelEnd = ""
|
||||
case "horizontal":
|
||||
fallthrough
|
||||
default:
|
||||
labelStart = "{"
|
||||
labelEnd = "}"
|
||||
}
|
||||
|
||||
existingNodes := map[string]bool{}
|
||||
|
||||
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error {
|
||||
@@ -26,9 +41,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "darkgoldenrod1",
|
||||
"label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}",
|
||||
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "),
|
||||
strings.Join(repo.Architectures, ", "), repo.NumPackages()),
|
||||
"label": fmt.Sprintf("%sMirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d%s", labelStart, repo.Name, repo.ArchiveRoot,
|
||||
repo.Distribution, strings.Join(repo.Components, ", "),
|
||||
strings.Join(repo.Architectures, ", "), repo.NumPackages(), labelEnd),
|
||||
})
|
||||
existingNodes[repo.UUID] = true
|
||||
return nil
|
||||
@@ -48,8 +63,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "mediumseagreen",
|
||||
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
|
||||
repo.Name, repo.Comment, repo.NumPackages()),
|
||||
"label": fmt.Sprintf("%sRepo %s|comment: %s|pkgs: %d%s", labelStart,
|
||||
repo.Name, repo.Comment, repo.NumPackages(), labelEnd),
|
||||
})
|
||||
existingNodes[repo.UUID] = true
|
||||
return nil
|
||||
@@ -79,7 +94,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "cadetblue1",
|
||||
"label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()),
|
||||
"label": fmt.Sprintf("%sSnapshot %s|%s|pkgs: %d%s", labelStart,
|
||||
snapshot.Name, description, snapshot.NumPackages(), labelEnd),
|
||||
})
|
||||
|
||||
if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
|
||||
@@ -102,8 +118,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "darkolivegreen1",
|
||||
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution,
|
||||
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")),
|
||||
"label": fmt.Sprintf("%sPublished %s/%s|comp: %s|arch: %s%s", labelStart,
|
||||
repo.Prefix, repo.Distribution, strings.Join(repo.Components(), " "),
|
||||
strings.Join(repo.Architectures, ", "), labelEnd),
|
||||
})
|
||||
|
||||
for _, uuid := range repo.Sources {
|
||||
|
||||
@@ -5,11 +5,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/go-uuid/uuid"
|
||||
"github.com/ugorji/go/codec"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@@ -18,6 +13,13 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/smira/go-uuid/uuid"
|
||||
"github.com/ugorji/go/codec"
|
||||
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
)
|
||||
|
||||
type repoSourceItem struct {
|
||||
@@ -677,7 +679,14 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
|
||||
release["Components"] = strings.Join(p.Components(), " ")
|
||||
|
||||
for path, info := range indexes.generatedFiles {
|
||||
sortedPaths := make([]string, 0, len(indexes.generatedFiles))
|
||||
for path := range indexes.generatedFiles {
|
||||
sortedPaths = append(sortedPaths, path)
|
||||
}
|
||||
sort.Strings(sortedPaths)
|
||||
|
||||
for _, path := range sortedPaths {
|
||||
info := indexes.generatedFiles[path]
|
||||
release["MD5Sum"] += fmt.Sprintf(" %s %8d %s\n", info.MD5, info.Size, path)
|
||||
release["SHA1"] += fmt.Sprintf(" %s %8d %s\n", info.SHA1, info.Size, path)
|
||||
release["SHA256"] += fmt.Sprintf(" %s %8d %s\n", info.SHA256, info.Size, path)
|
||||
|
||||
23
deb/query.go
23
deb/query.go
@@ -72,6 +72,9 @@ type DependencyQuery struct {
|
||||
Dep Dependency
|
||||
}
|
||||
|
||||
// MatchAllQuery is query that matches all the packages
|
||||
type MatchAllQuery struct{}
|
||||
|
||||
// Matches if any of L, R matches
|
||||
func (q *OrQuery) Matches(pkg PackageLike) bool {
|
||||
return q.L.Matches(pkg) || q.R.Matches(pkg)
|
||||
@@ -275,3 +278,23 @@ func (q *PkgQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
func (q *PkgQuery) String() string {
|
||||
return fmt.Sprintf("%s_%s_%s", q.Pkg, q.Version, q.Arch)
|
||||
}
|
||||
|
||||
// Matches on specific properties
|
||||
func (q *MatchAllQuery) Matches(pkg PackageLike) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Fast is always true for match all query
|
||||
func (q *MatchAllQuery) Fast(list PackageCatalog) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Query looks up specific package
|
||||
func (q *MatchAllQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
return list.Scan(q)
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *MatchAllQuery) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ ok:
|
||||
|
||||
// DownloadPackageIndexes downloads & parses package index files
|
||||
func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory,
|
||||
ignoreMismatch bool) error {
|
||||
ignoreMismatch bool, maxTries int) error {
|
||||
if repo.packageList != nil {
|
||||
panic("packageList != nil")
|
||||
}
|
||||
@@ -433,7 +433,7 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
|
||||
|
||||
for _, info := range packagesURLs {
|
||||
url, kind := info[0], info[1]
|
||||
packagesReader, packagesFile, err := http.DownloadTryCompression(d, url, repo.ReleaseFiles, ignoreMismatch)
|
||||
packagesReader, packagesFile, err := http.DownloadTryCompression(d, url, repo.ReleaseFiles, ignoreMismatch, maxTries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.HTTPError{Code: 404})
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
|
||||
|
||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false)
|
||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false, 1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
@@ -293,7 +293,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", &http.HTTPError{Code: 404})
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile)
|
||||
|
||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false)
|
||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false, 1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
@@ -334,7 +334,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
||||
err := s.flat.Fetch(downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, true)
|
||||
err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, true, 1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
@@ -367,7 +367,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
||||
err := s.flat.Fetch(downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, true)
|
||||
err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, true, 1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ type downloadTask struct {
|
||||
result chan<- error
|
||||
expected utils.ChecksumInfo
|
||||
ignoreMismatch bool
|
||||
triesLeft int
|
||||
}
|
||||
|
||||
// NewDownloader creates new instance of Downloader which specified number
|
||||
@@ -127,13 +128,13 @@ func (downloader *downloaderImpl) GetProgress() aptly.Progress {
|
||||
|
||||
// Download starts new download task
|
||||
func (downloader *downloaderImpl) Download(url string, destination string, result chan<- error) {
|
||||
downloader.DownloadWithChecksum(url, destination, result, utils.ChecksumInfo{Size: -1}, false)
|
||||
downloader.DownloadWithChecksum(url, destination, result, utils.ChecksumInfo{Size: -1}, false, 1)
|
||||
}
|
||||
|
||||
// DownloadWithChecksum starts new download task with checksum verification
|
||||
func (downloader *downloaderImpl) DownloadWithChecksum(url string, destination string, result chan<- error,
|
||||
expected utils.ChecksumInfo, ignoreMismatch bool) {
|
||||
downloader.queue <- &downloadTask{url: url, destination: destination, result: result, expected: expected, ignoreMismatch: ignoreMismatch}
|
||||
expected utils.ChecksumInfo, ignoreMismatch bool, maxTries int) {
|
||||
downloader.queue <- &downloadTask{url: url, destination: destination, result: result, expected: expected, ignoreMismatch: ignoreMismatch, triesLeft: maxTries}
|
||||
}
|
||||
|
||||
// handleTask processes single download task
|
||||
@@ -153,32 +154,59 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
|
||||
req.URL.RawQuery = ""
|
||||
}
|
||||
|
||||
resp, err := downloader.client.Do(req)
|
||||
|
||||
var temppath string
|
||||
for task.triesLeft > 0 {
|
||||
|
||||
temppath, err = downloader.downloadTask(req, task)
|
||||
|
||||
if err != nil {
|
||||
task.triesLeft--
|
||||
} else {
|
||||
// successful download
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// still an error after retrying, giving up
|
||||
if err != nil {
|
||||
task.result <- err
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Rename(temppath, task.destination)
|
||||
if err != nil {
|
||||
os.Remove(temppath)
|
||||
task.result <- fmt.Errorf("%s: %s", task.url, err)
|
||||
return
|
||||
}
|
||||
|
||||
task.result <- nil
|
||||
}
|
||||
|
||||
func (downloader *downloaderImpl) downloadTask(req *http.Request, task *downloadTask) (string, error) {
|
||||
resp, err := downloader.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: %s", task.url, err)
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
task.result <- &HTTPError{Code: resp.StatusCode, URL: task.url}
|
||||
return
|
||||
return "", &HTTPError{Code: resp.StatusCode, URL: task.url}
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(task.destination), 0777)
|
||||
if err != nil {
|
||||
task.result <- fmt.Errorf("%s: %s", task.url, err)
|
||||
return
|
||||
return "", fmt.Errorf("%s: %s", task.url, err)
|
||||
}
|
||||
|
||||
temppath := task.destination + ".down"
|
||||
|
||||
outfile, err := os.Create(temppath)
|
||||
if err != nil {
|
||||
task.result <- fmt.Errorf("%s: %s", task.url, err)
|
||||
return
|
||||
return "", fmt.Errorf("%s: %s", task.url, err)
|
||||
}
|
||||
defer outfile.Close()
|
||||
|
||||
@@ -194,8 +222,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
os.Remove(temppath)
|
||||
task.result <- fmt.Errorf("%s: %s", task.url, err)
|
||||
return
|
||||
return "", fmt.Errorf("%s: %s", task.url, err)
|
||||
}
|
||||
|
||||
if task.expected.Size != -1 {
|
||||
@@ -218,20 +245,12 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
|
||||
downloader.progress.Printf("WARNING: %s\n", err.Error())
|
||||
} else {
|
||||
os.Remove(temppath)
|
||||
task.result <- err
|
||||
return
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Rename(temppath, task.destination)
|
||||
if err != nil {
|
||||
os.Remove(temppath)
|
||||
task.result <- fmt.Errorf("%s: %s", task.url, err)
|
||||
return
|
||||
}
|
||||
|
||||
task.result <- nil
|
||||
return temppath, nil
|
||||
}
|
||||
|
||||
// process implements download thread in goroutine
|
||||
@@ -253,13 +272,13 @@ func (downloader *downloaderImpl) process() {
|
||||
//
|
||||
// Temporary file would be already removed, so no need to cleanup
|
||||
func DownloadTemp(downloader aptly.Downloader, url string) (*os.File, error) {
|
||||
return DownloadTempWithChecksum(downloader, url, utils.ChecksumInfo{Size: -1}, false)
|
||||
return DownloadTempWithChecksum(downloader, url, utils.ChecksumInfo{Size: -1}, false, 1)
|
||||
}
|
||||
|
||||
// DownloadTempWithChecksum is a DownloadTemp with checksum verification
|
||||
//
|
||||
// Temporary file would be already removed, so no need to cleanup
|
||||
func DownloadTempWithChecksum(downloader aptly.Downloader, url string, expected utils.ChecksumInfo, ignoreMismatch bool) (*os.File, error) {
|
||||
func DownloadTempWithChecksum(downloader aptly.Downloader, url string, expected utils.ChecksumInfo, ignoreMismatch bool, maxTries int) (*os.File, error) {
|
||||
tempdir, err := ioutil.TempDir(os.TempDir(), "aptly")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -274,7 +293,7 @@ func DownloadTempWithChecksum(downloader aptly.Downloader, url string, expected
|
||||
}
|
||||
|
||||
ch := make(chan error, 1)
|
||||
downloader.DownloadWithChecksum(url, tempfile, ch, expected, ignoreMismatch)
|
||||
downloader.DownloadWithChecksum(url, tempfile, ch, expected, ignoreMismatch, maxTries)
|
||||
|
||||
err = <-ch
|
||||
|
||||
@@ -311,7 +330,7 @@ var compressionMethods = []struct {
|
||||
|
||||
// DownloadTryCompression tries to download from URL .bz2, .gz and raw extension until
|
||||
// it finds existing file.
|
||||
func DownloadTryCompression(downloader aptly.Downloader, url string, expectedChecksums map[string]utils.ChecksumInfo, ignoreMismatch bool) (io.Reader, *os.File, error) {
|
||||
func DownloadTryCompression(downloader aptly.Downloader, url string, expectedChecksums map[string]utils.ChecksumInfo, ignoreMismatch bool, maxTries int) (io.Reader, *os.File, error) {
|
||||
var err error
|
||||
|
||||
for _, method := range compressionMethods {
|
||||
@@ -322,7 +341,7 @@ func DownloadTryCompression(downloader aptly.Downloader, url string, expectedChe
|
||||
|
||||
for suffix, expected := range expectedChecksums {
|
||||
if strings.HasSuffix(tryURL, suffix) {
|
||||
file, err = DownloadTempWithChecksum(downloader, tryURL, expected, ignoreMismatch)
|
||||
file, err = DownloadTempWithChecksum(downloader, tryURL, expected, ignoreMismatch, maxTries)
|
||||
foundChecksum = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -95,38 +95,38 @@ func (s *DownloaderSuite) TestDownloadWithChecksum(c *C) {
|
||||
defer d.Shutdown()
|
||||
ch := make(chan error)
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{}, false)
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{}, false, 1)
|
||||
res := <-ch
|
||||
c.Assert(res, ErrorMatches, ".*size check mismatch 12 != 0")
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "abcdef"}, false)
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "abcdef"}, false, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, ErrorMatches, ".*md5 hash mismatch \"a1acb0fe91c7db45ec4d775192ec5738\" != \"abcdef\"")
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "abcdef"}, true)
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "abcdef"}, true, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, IsNil)
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738"}, false)
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738"}, false, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, IsNil)
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738", SHA1: "abcdef"}, false)
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738", SHA1: "abcdef"}, false, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, ErrorMatches, ".*sha1 hash mismatch \"921893bae6ad6fd818401875d6779254ef0ff0ec\" != \"abcdef\"")
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738",
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec"}, false)
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec"}, false, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, IsNil)
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738",
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec", SHA256: "abcdef"}, false)
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec", SHA256: "abcdef"}, false, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, ErrorMatches, ".*sha256 hash mismatch \"b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac\" != \"abcdef\"")
|
||||
|
||||
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738",
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec", SHA256: "b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac"}, false)
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec", SHA256: "b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac"}, false, 1)
|
||||
res = <-ch
|
||||
c.Assert(res, IsNil)
|
||||
}
|
||||
@@ -183,11 +183,11 @@ func (s *DownloaderSuite) TestDownloadTempWithChecksum(c *C) {
|
||||
defer d.Shutdown()
|
||||
|
||||
f, err := DownloadTempWithChecksum(d, s.url+"/test", utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738",
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec", SHA256: "b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac"}, false)
|
||||
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec", SHA256: "b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac"}, false, 1)
|
||||
defer f.Close()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, err = DownloadTempWithChecksum(d, s.url+"/test", utils.ChecksumInfo{Size: 13}, false)
|
||||
_, err = DownloadTempWithChecksum(d, s.url+"/test", utils.ChecksumInfo{Size: 13}, false, 1)
|
||||
c.Assert(err, ErrorMatches, ".*size check mismatch 12 != 13")
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
|
||||
buf = make([]byte, 4)
|
||||
d := NewFakeDownloader()
|
||||
d.ExpectResponse("http://example.com/file.bz2", bzipData)
|
||||
r, file, err := DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false)
|
||||
r, file, err := DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false, 1)
|
||||
c.Assert(err, IsNil)
|
||||
defer file.Close()
|
||||
io.ReadFull(r, buf)
|
||||
@@ -232,7 +232,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
|
||||
d = NewFakeDownloader()
|
||||
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
|
||||
d.ExpectResponse("http://example.com/file.gz", gzipData)
|
||||
r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false)
|
||||
r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false, 1)
|
||||
c.Assert(err, IsNil)
|
||||
defer file.Close()
|
||||
io.ReadFull(r, buf)
|
||||
@@ -245,7 +245,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
|
||||
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
|
||||
d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404})
|
||||
d.ExpectResponse("http://example.com/file", rawData)
|
||||
r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false)
|
||||
r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false, 1)
|
||||
c.Assert(err, IsNil)
|
||||
defer file.Close()
|
||||
io.ReadFull(r, buf)
|
||||
@@ -257,21 +257,21 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
|
||||
d = NewFakeDownloader()
|
||||
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
|
||||
d.ExpectResponse("http://example.com/file.gz", "x")
|
||||
r, file, err = DownloadTryCompression(d, "http://example.com/file", nil, true)
|
||||
r, file, err = DownloadTryCompression(d, "http://example.com/file", nil, true, 1)
|
||||
c.Assert(err, ErrorMatches, "unexpected EOF")
|
||||
c.Assert(d.Empty(), Equals, true)
|
||||
}
|
||||
|
||||
func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) {
|
||||
d := NewFakeDownloader()
|
||||
_, _, err := DownloadTryCompression(d, "http://example.com/file", nil, true)
|
||||
_, _, err := DownloadTryCompression(d, "http://example.com/file", nil, true, 1)
|
||||
c.Assert(err, ErrorMatches, "unexpected request.*")
|
||||
|
||||
d = NewFakeDownloader()
|
||||
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
|
||||
d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404})
|
||||
d.ExpectError("http://example.com/file", errors.New("403"))
|
||||
_, _, err = DownloadTryCompression(d, "http://example.com/file", nil, true)
|
||||
_, _, err = DownloadTryCompression(d, "http://example.com/file", nil, true, 1)
|
||||
c.Assert(err, ErrorMatches, "403")
|
||||
|
||||
d = NewFakeDownloader()
|
||||
@@ -283,6 +283,6 @@ func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) {
|
||||
"file.gz": {Size: 7},
|
||||
"file": {Size: 7},
|
||||
}
|
||||
_, _, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false)
|
||||
_, _, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false, 1)
|
||||
c.Assert(err, ErrorMatches, "checksums don't match.*")
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (f *FakeDownloader) Empty() bool {
|
||||
}
|
||||
|
||||
// DownloadWithChecksum performs fake download by matching against first expectation in the queue or any expectation, with cheksum verification
|
||||
func (f *FakeDownloader) DownloadWithChecksum(url string, filename string, result chan<- error, expected utils.ChecksumInfo, ignoreMismatch bool) {
|
||||
func (f *FakeDownloader) DownloadWithChecksum(url string, filename string, result chan<- error, expected utils.ChecksumInfo, ignoreMismatch bool, maxTries int) {
|
||||
var expectation expectedRequest
|
||||
if len(f.expected) > 0 && f.expected[0].URL == url {
|
||||
expectation, f.expected = f.expected[0], f.expected[1:]
|
||||
@@ -116,7 +116,7 @@ func (f *FakeDownloader) DownloadWithChecksum(url string, filename string, resul
|
||||
|
||||
// Download performs fake download by matching against first expectation in the queue
|
||||
func (f *FakeDownloader) Download(url string, filename string, result chan<- error) {
|
||||
f.DownloadWithChecksum(url, filename, result, utils.ChecksumInfo{Size: -1}, false)
|
||||
f.DownloadWithChecksum(url, filename, result, utils.ChecksumInfo{Size: -1}, false, 1)
|
||||
}
|
||||
|
||||
// Shutdown does nothing
|
||||
|
||||
11
man/Makefile
Normal file
11
man/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
all: prepare generate
|
||||
|
||||
prepare:
|
||||
gem install specific_install
|
||||
gem specific_install -l smira/ronn
|
||||
|
||||
generate:
|
||||
cd .. && gom build -o man/gen man/gen.go
|
||||
./gen
|
||||
|
||||
.PHONY: prepare generate
|
||||
62
man/aptly.1
62
man/aptly.1
@@ -1,7 +1,7 @@
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "APTLY" "1" "November 2016" "" ""
|
||||
.TH "APTLY" "1" "February 2017" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBaptly\fR \- Debian repository management tool
|
||||
@@ -506,6 +506,10 @@ disable verification of Release file signatures
|
||||
\-\fBkeyring\fR=
|
||||
gpg keyring to use when verifying Release file (could be specified multiple times)
|
||||
.
|
||||
.TP
|
||||
\-\fBmax\-tries\fR=1
|
||||
max download tries till process fails with download error
|
||||
.
|
||||
.SH "RENAMES MIRROR"
|
||||
\fBaptly\fR \fBmirror\fR \fBrename\fR \fIold\-name\fR \fInew\-name\fR
|
||||
.
|
||||
@@ -550,12 +554,15 @@ download source packages in addition to binary packages
|
||||
download \.udeb packages (Debian installer support)
|
||||
.
|
||||
.SH "SEARCH MIRROR FOR PACKAGES MATCHING QUERY"
|
||||
\fBaptly\fR \fBmirror\fR \fBsearch\fR \fIname\fR \fIpackage\-query\fR
|
||||
\fBaptly\fR \fBmirror\fR \fBsearch\fR \fIname\fR [\fIpackage\-query\fR]
|
||||
.
|
||||
.P
|
||||
Command search displays list of packages in mirror that match package query
|
||||
.
|
||||
.P
|
||||
If query is not specified, all the packages are displayed\.
|
||||
.
|
||||
.P
|
||||
Example:
|
||||
.
|
||||
.IP "" 4
|
||||
@@ -819,12 +826,15 @@ Example:
|
||||
$ aptly repo rename wheezy\-min wheezy\-main
|
||||
.
|
||||
.SH "SEARCH REPO FOR PACKAGES MATCHING QUERY"
|
||||
\fBaptly\fR \fBrepo\fR \fBsearch\fR \fIname\fR \fIpackage\-query\fR
|
||||
\fBaptly\fR \fBrepo\fR \fBsearch\fR \fIname\fR [\fIpackage\-query\fR]
|
||||
.
|
||||
.P
|
||||
Command search displays list of packages in local repository that match package query
|
||||
.
|
||||
.P
|
||||
If query is not specified, all the packages are displayed\.
|
||||
.
|
||||
.P
|
||||
Example:
|
||||
.
|
||||
.IP "" 4
|
||||
@@ -1113,12 +1123,15 @@ Example:
|
||||
$ aptly snapshot rename wheezy\-min wheezy\-main
|
||||
.
|
||||
.SH "SEARCH SNAPSHOT FOR PACKAGES MATCHING QUERY"
|
||||
\fBaptly\fR \fBsnapshot\fR \fBsearch\fR \fIname\fR \fIpackage\-query\fR
|
||||
\fBaptly\fR \fBsnapshot\fR \fBsearch\fR \fIname\fR [\fIpackage\-query\fR]
|
||||
.
|
||||
.P
|
||||
Command search displays list of packages in snapshot that match package query
|
||||
.
|
||||
.P
|
||||
If query is not specified, all the packages are displayed\.
|
||||
.
|
||||
.P
|
||||
Example:
|
||||
.
|
||||
.IP "" 4
|
||||
@@ -1536,11 +1549,33 @@ don\(cqt generate Contents indexes
|
||||
\-\fBskip\-signing\fR=false
|
||||
don\(cqt sign Release files with GPG
|
||||
.
|
||||
.SH "SEARCH FOR PACKAGES MATCHING QUERY"
|
||||
\fBaptly\fR \fBpackage\fR \fBsearch\fR \fIpackage\-query\fR
|
||||
.SH "SHOWS DETAILS OF PUBLISHED REPOSITORY"
|
||||
\fBaptly\fR \fBpublish\fR \fBshow\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR]
|
||||
.
|
||||
.P
|
||||
Command search displays list of packages in whole DB that match package query
|
||||
Command show displays full information of a published repository\.
|
||||
.
|
||||
.P
|
||||
Example:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ aptly publish show wheezy
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "SEARCH FOR PACKAGES MATCHING QUERY"
|
||||
\fBaptly\fR \fBpackage\fR \fBsearch\fR [\fIpackage\-query\fR]
|
||||
.
|
||||
.P
|
||||
Command search displays list of packages in whole DB that match package query\.
|
||||
.
|
||||
.P
|
||||
If query is not specified, all the packages are displayed\.
|
||||
.
|
||||
.P
|
||||
Example:
|
||||
@@ -1689,6 +1724,10 @@ Options:
|
||||
render graph to specified format (png, svg, pdf, etc\.)
|
||||
.
|
||||
.TP
|
||||
\-\fBlayout\fR=horizontal
|
||||
create a more \(cqvertical\(cq or a more \(cqhorizontal\(cq graph layout
|
||||
.
|
||||
.TP
|
||||
\-\fBoutput\fR=
|
||||
specify output filename, default is to open result in viewer
|
||||
.
|
||||
@@ -1834,5 +1873,14 @@ Benoit Foucher (https://github\.com/bentoi)
|
||||
.IP "\[ci]" 4
|
||||
Geoffrey Thomas (https://github\.com/geofft)
|
||||
.
|
||||
.IP "\[ci]" 4
|
||||
Oliver Sauder (https://github\.com/sliverc)
|
||||
.
|
||||
.IP "\[ci]" 4
|
||||
Harald Sitter (https://github\.com/apachelogger)
|
||||
.
|
||||
.IP "\[ci]" 4
|
||||
Johannes Layher (https://github\.com/jola5)
|
||||
.
|
||||
.IP "" 0
|
||||
|
||||
|
||||
@@ -270,6 +270,14 @@ class BaseTest(object):
|
||||
if a != b:
|
||||
self.verify_match(a, b, match_prepare=pprint.pformat)
|
||||
|
||||
def check_ge(self, a, b):
|
||||
if not a >= b:
|
||||
raise Exception("%s is not greater or equal to %s" % (a, b))
|
||||
|
||||
def check_gt(self, a, b):
|
||||
if not a > b:
|
||||
raise Exception("%s is not greater to %s" % (a, b))
|
||||
|
||||
def check_in(self, item, l):
|
||||
if not item in l:
|
||||
raise Exception("item %r not in %r", item, l)
|
||||
|
||||
56122
system/t04_mirror/SearchMirror6Test_gold
Normal file
56122
system/t04_mirror/SearchMirror6Test_gold
Normal file
File diff suppressed because it is too large
Load Diff
@@ -43,3 +43,12 @@ class SearchMirror5Test(BaseTest):
|
||||
fixtureDB = True
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
runCmd = "aptly mirror search -format='{{.Package}}#{{.Version}}' wheezy-main '$$Architecture (i386), Name (% *-dev)'"
|
||||
|
||||
|
||||
class SearchMirror6Test(BaseTest):
|
||||
"""
|
||||
search mirror: no query
|
||||
"""
|
||||
fixtureDB = True
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
runCmd = "aptly mirror search -format='{{.Package}}#{{.Version}}' wheezy-main"
|
||||
|
||||
@@ -2,6 +2,8 @@ Name: snap1
|
||||
Created At: 2014-06-29 01:43:01 MSK
|
||||
Description: Snapshot from mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy
|
||||
Number of packages: 56121
|
||||
Sources:
|
||||
wheezy-main [repo]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Name: snap6
|
||||
Description: Snapshot from local repo [local-repo]
|
||||
Number of packages: 3
|
||||
Sources:
|
||||
local-repo [local]
|
||||
Packages:
|
||||
libboost-program-options-dev_1.49.0.1_i386
|
||||
pyspi_0.6.1-1.3_source
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Name: snap2
|
||||
Description: Filtered 'snap1', query was: 'mame unrar'
|
||||
Number of packages: 4
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
Packages:
|
||||
mame_0.146-5_amd64
|
||||
unrar_1:4.1.4-1_amd64
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Name: snap2
|
||||
Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4)'
|
||||
Number of packages: 9
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
Packages:
|
||||
init-system-helpers_1.18~bpo70+1_all
|
||||
libestr0_0.1.9-1~bpo70+1_amd64
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Name: snap2
|
||||
Description: Filtered 'snap1', query was: 'Priority (required) nginx xyz'
|
||||
Number of packages: 123
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
Packages:
|
||||
debconf_1.5.49_all
|
||||
debconf-i18n_1.5.49_all
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Name: snap2
|
||||
Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4), $Architecture (i386)'
|
||||
Number of packages: 10
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
Packages:
|
||||
init-system-helpers_1.18~bpo70+1_all
|
||||
libestr0_0.1.9-1~bpo70+1_i386
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 02:23:49 MSK
|
||||
Description: Merged from sources: 'snap1', 'snap2'
|
||||
Number of packages: 56782
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,10 @@ Name: snap4
|
||||
Created At: 2014-06-29 02:14:26 MSK
|
||||
Description: Merged from sources: 'snap1', 'snap2', 'snap3'
|
||||
Number of packages: 58968
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
snap3 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0.0.16-1~bpo70+1_all
|
||||
0ad-data-common_0.0.16-1~bpo70+1_all
|
||||
|
||||
@@ -2,6 +2,10 @@ Name: snap4
|
||||
Created At: 2014-06-29 02:15:01 MSK
|
||||
Description: Merged from sources: 'snap1', 'snap2', 'snap3'
|
||||
Number of packages: 58802
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
snap3 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0.0.16-1~bpo70+1_all
|
||||
0ad-data-common_0.0.16-1~bpo70+1_all
|
||||
|
||||
@@ -2,6 +2,10 @@ Name: snap4
|
||||
Created At: 2014-06-29 02:16:12 MSK
|
||||
Description: Merged from sources: 'snap3', 'snap2', 'snap1'
|
||||
Number of packages: 58817
|
||||
Sources:
|
||||
snap3 [snapshot]
|
||||
snap2 [snapshot]
|
||||
snap1 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
0ad-data-common_0.0.16-1~bpo70+1_all
|
||||
|
||||
@@ -2,6 +2,10 @@ Name: snap4
|
||||
Created At: 2014-06-29 02:19:14 MSK
|
||||
Description: Merged from sources: 'snap1', 'snap2', 'snap3'
|
||||
Number of packages: 61524
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
snap3 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0.0.16-1~bpo70+1_all
|
||||
0ad-data_0~r11863-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 02:00:49 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
|
||||
Number of packages: 73295
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 02:03:59 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
|
||||
Number of packages: 56130
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 02:31:20 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'mame unrar'
|
||||
Number of packages: 56125
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 01:50:10 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
|
||||
Number of packages: 56126
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 01:52:15 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
|
||||
Number of packages: 56121
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 01:57:44 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'lunar-landing mars-landing (>= 1.0)'
|
||||
Number of packages: 56121
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
@@ -2,6 +2,9 @@ Name: snap3
|
||||
Created At: 2014-06-29 01:58:24 MSK
|
||||
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
|
||||
Number of packages: 56131
|
||||
Sources:
|
||||
snap1 [snapshot]
|
||||
snap2 [snapshot]
|
||||
Packages:
|
||||
0ad-data_0~r11863-1_all
|
||||
2ping_2.0-1_all
|
||||
|
||||
56122
system/t05_snapshot/SearchSnapshot7Test_gold
Normal file
56122
system/t05_snapshot/SearchSnapshot7Test_gold
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@ Name: snap1
|
||||
Created At: 2014-06-28 03:01:02 MSK
|
||||
Description: Snapshot from mirror [wheezy-non-free]: http://mirror.yandex.ru/debian/ wheezy
|
||||
Number of packages: 661
|
||||
Sources:
|
||||
wheezy-non-free [repo]
|
||||
Packages:
|
||||
abs-guide_6.5-1_all
|
||||
album_4.06-2_all
|
||||
|
||||
@@ -2,3 +2,5 @@ Name: snap1
|
||||
Created At: 2014-01-24 13:06:43 MSK
|
||||
Description: Snapshot from mirror [wheezy-non-free]: http://mirror.yandex.ru/debian/ wheezy
|
||||
Number of packages: 661
|
||||
Sources:
|
||||
wheezy-non-free [repo]
|
||||
|
||||
@@ -57,3 +57,12 @@ class SearchSnapshot6Test(BaseTest):
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"]
|
||||
runCmd = "aptly snapshot search -format='{{.Package}}#{{.Version}}' wheezy-main '$$Architecture (i386), Name (% *-dev)'"
|
||||
|
||||
class SearchSnapshot7Test(BaseTest):
|
||||
"""
|
||||
search snapshot: without query
|
||||
"""
|
||||
fixtureDB = True
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"]
|
||||
runCmd = "aptly snapshot search -format='{{.Package}}#{{.Version}}' wheezy-main"
|
||||
|
||||
5
system/t06_publish/PublishShow1Test_gold
Normal file
5
system/t06_publish/PublishShow1Test_gold
Normal file
@@ -0,0 +1,5 @@
|
||||
Prefix: .
|
||||
Distribution: maverick
|
||||
Architectures: amd64 i386
|
||||
Sources:
|
||||
main: snap1 [snapshot]
|
||||
5
system/t06_publish/PublishShow2Test_gold
Normal file
5
system/t06_publish/PublishShow2Test_gold
Normal file
@@ -0,0 +1,5 @@
|
||||
Prefix: ppa/smira
|
||||
Distribution: maverick
|
||||
Architectures: amd64 i386
|
||||
Sources:
|
||||
main: snap1 [snapshot]
|
||||
@@ -3,6 +3,7 @@ Testing publishing snapshots
|
||||
"""
|
||||
|
||||
from .drop import *
|
||||
from .show import *
|
||||
from .list import *
|
||||
from .repo import *
|
||||
from .snapshot import *
|
||||
|
||||
27
system/t06_publish/show.py
Normal file
27
system/t06_publish/show.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from lib import BaseTest
|
||||
|
||||
|
||||
class PublishShow1Test(BaseTest):
|
||||
"""
|
||||
publish show: existing snapshot
|
||||
"""
|
||||
fixtureDB = True
|
||||
fixturePool = True
|
||||
fixtureCmds = [
|
||||
"aptly snapshot create snap1 from mirror gnuplot-maverick",
|
||||
"aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1",
|
||||
]
|
||||
runCmd = "aptly publish show maverick"
|
||||
|
||||
|
||||
class PublishShow2Test(BaseTest):
|
||||
"""
|
||||
publish show: under prefix
|
||||
"""
|
||||
fixtureDB = True
|
||||
fixturePool = True
|
||||
fixtureCmds = [
|
||||
"aptly snapshot create snap1 from mirror gnuplot-maverick",
|
||||
"aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1 ppa/smira",
|
||||
]
|
||||
runCmd = "aptly publish show maverick ppa/smira"
|
||||
1
system/t07_serve/RootDirInaccessible_gold
Normal file
1
system/t07_serve/RootDirInaccessible_gold
Normal file
@@ -0,0 +1 @@
|
||||
ERROR: '/root' is inaccessible, check access rights
|
||||
@@ -8,9 +8,24 @@ import signal
|
||||
import subprocess
|
||||
import shlex
|
||||
import time
|
||||
import errno
|
||||
|
||||
from lib import BaseTest
|
||||
from socket import error as socket_error
|
||||
|
||||
class RootDirInaccessible(BaseTest):
|
||||
"""
|
||||
serve command aborts if rootDir is inaccessible
|
||||
"""
|
||||
fixtureDB = False
|
||||
fixturePool = False
|
||||
|
||||
configOverride = {
|
||||
"rootDir": "/root" # any directory that exists but is not writable
|
||||
}
|
||||
|
||||
runCmd = "aptly serve -listen=127.0.0.1:8765"
|
||||
expectedCode = 1
|
||||
|
||||
class Serve1Test(BaseTest):
|
||||
"""
|
||||
|
||||
56122
system/t09_repo/SearchRepo6Test_gold
Normal file
56122
system/t09_repo/SearchRepo6Test_gold
Normal file
File diff suppressed because it is too large
Load Diff
@@ -47,3 +47,12 @@ class SearchRepo5Test(BaseTest):
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
fixtureCmds = ["aptly repo create wheezy-main", "aptly repo import wheezy-main wheezy-main Name"]
|
||||
runCmd = "aptly repo search -format='{{.Package}}#{{.Version}}' wheezy-main '$$Architecture (i386), Name (% *-dev)'"
|
||||
|
||||
class SearchRepo6Test(BaseTest):
|
||||
"""
|
||||
search repo: without query
|
||||
"""
|
||||
fixtureDB = True
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
fixtureCmds = ["aptly repo create wheezy-main", "aptly repo import wheezy-main wheezy-main Name"]
|
||||
runCmd = "aptly repo search wheezy-main"
|
||||
|
||||
80414
system/t11_package/SearchPackage6Test_gold
Normal file
80414
system/t11_package/SearchPackage6Test_gold
Normal file
File diff suppressed because it is too large
Load Diff
@@ -42,3 +42,11 @@ class SearchPackage5Test(BaseTest):
|
||||
fixtureDB = True
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
runCmd = "aptly package search -format='{{.Package}}#{{.Version}}' '$$Architecture (i386), Name (% *-dev)'"
|
||||
|
||||
class SearchPackage6Test(BaseTest):
|
||||
"""
|
||||
search package: no query
|
||||
"""
|
||||
fixtureDB = True
|
||||
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
|
||||
runCmd = "aptly package search"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from api_lib import APITest
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
class GraphAPITest(APITest):
|
||||
@@ -15,3 +16,32 @@ class GraphAPITest(APITest):
|
||||
resp = self.get("/api/graph.svg")
|
||||
self.check_equal(resp.headers["Content-Type"], "image/svg+xml")
|
||||
self.check_equal(resp.content[:4], '<?xm')
|
||||
|
||||
resp = self.get("/api/graph.dot")
|
||||
self.check_equal(resp.headers["Content-Type"], "text/plain; charset=utf-8")
|
||||
self.check_equal(resp.content[:13], 'digraph aptly')
|
||||
|
||||
# basic test of layout:
|
||||
# horizontal should be wider than vertical
|
||||
# vertical should be higher than horizontal
|
||||
# for this to work we need at couple of repos
|
||||
tempRepos = [self.random_name() for r in range(3)]
|
||||
for repo in tempRepos:
|
||||
self.check_equal(self.post("/api/repos", json={"Name": repo, "Comment": "graph test repo"}).status_code, 201)
|
||||
|
||||
horizontal = self.get("/api/graph.svg?layout=horizontal").content
|
||||
vertical = self.get("/api/graph.svg?layout=vertical").content
|
||||
horizontalWidth = int(ET.fromstring(horizontal).get('width').replace("pt",""))
|
||||
horizontalHeight = int(ET.fromstring(horizontal).get('height').replace("pt",""))
|
||||
verticalWidth = int(ET.fromstring(vertical).get('width').replace("pt",""))
|
||||
verticalHeight = int(ET.fromstring(vertical).get('height').replace("pt",""))
|
||||
|
||||
self.check_gt(horizontalWidth, verticalWidth)
|
||||
self.check_gt(verticalHeight, horizontalHeight)
|
||||
|
||||
# make sure our default layout is horizontal
|
||||
self.check_equal(horizontal, self.get("/api/graph.svg").content)
|
||||
|
||||
# remove the repos again
|
||||
for repo in tempRepos:
|
||||
self.check_equal(self.delete("/api/repos/" + repo, params={"force": "1"}).status_code, 200)
|
||||
|
||||
@@ -30,15 +30,13 @@ func (s *CompressSuite) TestCompress(c *C) {
|
||||
err := CompressFile(s.tempfile, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
buf := make([]byte, len(testString))
|
||||
|
||||
file, err := os.Open(s.tempfile.Name() + ".gz")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
gzReader, err := gzip.NewReader(file)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, err = gzReader.Read(buf)
|
||||
buf, err := ioutil.ReadAll(gzReader)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
gzReader.Close()
|
||||
|
||||
@@ -1,2 +1,23 @@
|
||||
// Package utils collects various services: simple operations, compression, etc.
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// check if directory exists and is accessible
|
||||
func DirIsAccessible(filename string) error {
|
||||
_, err := os.Stat(filename);
|
||||
if err != nil {
|
||||
if ! os.IsNotExist(err) {
|
||||
return fmt.Errorf("Something went wrong, %v", err)
|
||||
}
|
||||
} else {
|
||||
if unix.Access(filename, unix.W_OK) != nil {
|
||||
return fmt.Errorf("'%s' is inaccessible, check access rights", filename)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user