This commit is contained in:
Charles Hsu
2017-02-16 23:14:02 +08:00
73 changed files with 249562 additions and 133 deletions
+14
View 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
View 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`
+6 -2
View File
@@ -27,8 +27,12 @@ coverage*.out
*.pyc *.pyc
_vendor/ vendor/
xc-out/
root/
gen gen
man/aptly.1.html man/aptly.1.html
man/aptly.1.ronn man/aptly.1.ronn
.goxc.local.json
+6 -4
View File
@@ -2,10 +2,12 @@
"AppName": "aptly", "AppName": "aptly",
"ArtifactsDest": "xc-out/", "ArtifactsDest": "xc-out/",
"TasksExclude": [ "TasksExclude": [
"rmbin" "rmbin",
"go-test",
"go-vet"
], ],
"TasksAppend": [ "TasksAppend": [
"bintray" "bintray"
], ],
"TaskSettings": { "TaskSettings": {
"deb": { "deb": {
@@ -33,6 +35,6 @@
}, },
"Arch": "386 amd64", "Arch": "386 amd64",
"Os": "linux darwin freebsd", "Os": "linux darwin freebsd",
"MainDirsExclude": "man,_vendor", "MainDirsExclude": "man,vendor",
"ConfigVersion": "0.9" "ConfigVersion": "0.9"
} }
+6 -1
View File
@@ -3,8 +3,8 @@ sudo: false
language: go language: go
go: go:
- 1.5
- 1.6 - 1.6
- 1.7
- tip - tip
addons: addons:
@@ -21,7 +21,12 @@ env:
before_install: before_install:
- virtualenv env - virtualenv env
- . env/bin/activate - . env/bin/activate
- pip install six packaging appdirs
- pip install -U pip setuptools
- pip install boto requests python-swiftclient - 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: install:
- make prepare - make prepare
+3
View File
@@ -22,4 +22,7 @@ List of contributors, in chronological order:
* Phil Frost (https://github.com/bitglue) * Phil Frost (https://github.com/bitglue)
* Benoit Foucher (https://github.com/bentoi) * Benoit Foucher (https://github.com/bentoi)
* Geoffrey Thomas (https://github.com/geofft) * 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) * Charles Hsu (https://github.com/charz)
+74
View 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
View File
@@ -5,6 +5,7 @@ gom 'github.com/cheggaaa/pb', :commit => '2c1b74620cc58a81ac152ee2d322e28c806d81
gom 'github.com/DisposaBoy/JsonConfigReader', :commit => '33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4' gom 'github.com/DisposaBoy/JsonConfigReader', :commit => '33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4'
gom 'github.com/gin-gonic/gin', :commit => 'b1758d3bfa09e61ddbc1c9a627e936eec6a170de' gom 'github.com/gin-gonic/gin', :commit => 'b1758d3bfa09e61ddbc1c9a627e936eec6a170de'
gom 'github.com/go-ini/ini', :commit => 'afbd495e5aaea13597b5e14fe514ddeaa4d76fc3' 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/jlaffaye/ftp', :commit => 'fec71e62e457557fbe85cefc847a048d57815d76'
gom 'github.com/jmespath/go-jmespath', :commit => '0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74' gom 'github.com/jmespath/go-jmespath', :commit => '0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74'
gom 'github.com/julienschmidt/httprouter', :commit => '46807412fe50aaceb73bb57061c2230fd26a1640' 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/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1'
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08' gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
gom 'golang.org/x/crypto/ssh/terminal', :commit => 'a7ead6ddf06233883deca151dffaef2effbf498f' gom 'golang.org/x/crypto/ssh/terminal', :commit => 'a7ead6ddf06233883deca151dffaef2effbf498f'
gom 'golang.org/x/sys/unix', :commit => '7a6e5648d140666db5d920909e082ca00a87ba2c'
group :test do group :test do
gom 'gopkg.in/check.v1' gom 'gopkg.in/check.v1'
+8 -4
View File
@@ -1,9 +1,10 @@
GOVERSION=$(shell go version | awk '{print $$3;}') GOVERSION=$(shell go version | awk '{print $$3;}')
PACKAGES=context database deb files http query swift s3 utils 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 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 GOM_ENVIRONMENT=-test
PYTHON?=python PYTHON?=python
TESTS?=
ifeq ($(GOVERSION), devel) ifeq ($(GOVERSION), devel)
TRAVIS_TARGET=coveralls TRAVIS_TARGET=coveralls
@@ -45,7 +46,7 @@ install:
system-test: 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-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long $(TESTS)
travis: $(TRAVIS_TARGET) system-test 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/ && 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 && gom -production install
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf 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 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) (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) tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
@@ -80,4 +81,7 @@ goxc:
gzip root/usr/share/man/man1/aptly.1 gzip root/usr/share/man/man1/aptly.1
gom exec goxc -pv=$(VERSION) -max-processors=4 $(GOXC_OPTS) gom exec goxc -pv=$(VERSION) -max-processors=4 $(GOXC_OPTS)
.PHONY: coverage.out man:
make -C man
.PHONY: coverage.out man
+2 -1
View File
@@ -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/>`_. 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 go get -u github.com/mattn/gom
mkdir -p $GOPATH/src/github.com/smira/aptly mkdir -p $GOPATH/src/github.com/smira/aptly
@@ -107,6 +107,7 @@ With configuration management systems:
CLI for aptly API: CLI for aptly API:
- `Ruby aptly CLI/library <https://github.com/sepulworld/aptly_cli>`_ by Zane Williamson - `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: Scala sbt:
+10 -2
View File
@@ -11,7 +11,7 @@ import (
"os/exec" "os/exec"
) )
// GET /api/graph.:ext // GET /api/graph.:ext?layout=[vertical|horizontal(default)]
func apiGraph(c *gin.Context) { func apiGraph(c *gin.Context) {
var ( var (
err error err error
@@ -19,6 +19,7 @@ func apiGraph(c *gin.Context) {
) )
ext := c.Params.ByName("ext") ext := c.Params.ByName("ext")
layout := c.Request.URL.Query().Get("layout")
factory := context.CollectionFactory() factory := context.CollectionFactory()
@@ -31,7 +32,7 @@ func apiGraph(c *gin.Context) {
factory.PublishedRepoCollection().RLock() factory.PublishedRepoCollection().RLock()
defer factory.PublishedRepoCollection().RUnlock() defer factory.PublishedRepoCollection().RUnlock()
graph, err := deb.BuildGraph(factory) graph, err := deb.BuildGraph(factory, layout)
if err != nil { if err != nil {
c.JSON(500, err) c.JSON(500, err)
return return
@@ -39,6 +40,13 @@ func apiGraph(c *gin.Context) {
buf := bytes.NewBufferString(graph.String()) 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 := exec.Command("dot", "-T"+ext)
command.Stderr = os.Stderr command.Stderr = os.Stderr
+9 -9
View File
@@ -60,9 +60,9 @@ func apiReposCreate(c *gin.Context) {
// PUT /api/repos/:name // PUT /api/repos/:name
func apiReposEdit(c *gin.Context) { func apiReposEdit(c *gin.Context) {
var b struct { var b struct {
Comment string Comment *string
DefaultDistribution string DefaultDistribution *string
DefaultComponent string DefaultComponent *string
} }
if !c.Bind(&b) { if !c.Bind(&b) {
@@ -79,14 +79,14 @@ func apiReposEdit(c *gin.Context) {
return return
} }
if b.Comment != "" { if b.Comment != nil {
repo.Comment = b.Comment repo.Comment = *b.Comment
} }
if b.DefaultDistribution != "" { if b.DefaultDistribution != nil {
repo.DefaultDistribution = b.DefaultDistribution repo.DefaultDistribution = *b.DefaultDistribution
} }
if b.DefaultComponent != "" { if b.DefaultComponent != nil {
repo.DefaultComponent = b.DefaultComponent repo.DefaultComponent = *b.DefaultComponent
} }
err = collection.Update(repo) err = collection.Update(repo)
+1 -1
View File
@@ -82,7 +82,7 @@ type Downloader interface {
// Download starts new download task // Download starts new download task
Download(url string, destination string, result chan<- error) Download(url string, destination string, result chan<- error)
// DownloadWithChecksum starts new download task with checksum verification // 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 pauses task processing
Pause() Pause()
// Resume resumes task processing // Resume resumes task processing
+12
View File
@@ -3,6 +3,7 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/smira/aptly/api" "github.com/smira/aptly/api"
"github.com/smira/aptly/utils"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"net/http" "net/http"
@@ -18,6 +19,17 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
return commander.ErrCommandError 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() listen := context.Flags().Lookup("listen").Value.String()
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen) fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
+5 -1
View File
@@ -21,8 +21,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
layout := context.Flags().Lookup("layout").Value.String()
fmt.Printf("Generating graph...\n") fmt.Printf("Generating graph...\n")
graph, err := deb.BuildGraph(context.CollectionFactory()) graph, err := deb.BuildGraph(context.CollectionFactory(), layout)
if err != nil { if err != nil {
return err return err
} }
@@ -108,6 +111,7 @@ Example:
cmd.Flag.String("format", "png", "render graph to specified format (png, svg, pdf, etc.)") 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("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 return cmd
} }
+3 -1
View File
@@ -8,11 +8,13 @@ import (
func makeCmdMirrorSearch() *commander.Command { func makeCmdMirrorSearch() *commander.Command {
cmd := &commander.Command{ cmd := &commander.Command{
Run: aptlySnapshotMirrorRepoSearch, Run: aptlySnapshotMirrorRepoSearch,
UsageLine: "search <name> <package-query>", UsageLine: "search <name> [<package-query>]",
Short: "search mirror for packages matching query", Short: "search mirror for packages matching query",
Long: ` Long: `
Command search displays list of packages in mirror that match package query Command search displays list of packages in mirror that match package query
If query is not specified, all the packages are displayed.
Example: Example:
$ aptly mirror search wheezy-main '$Architecture (i386), Name (% *-dev)' $ aptly mirror search wheezy-main '$Architecture (i386), Name (% *-dev)'
+4 -2
View File
@@ -40,6 +40,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
} }
ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool) ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
maxTries := context.Flags().Lookup("max-tries").Value.Get().(int)
verifier, err := getVerifier(context.Flags()) verifier, err := getVerifier(context.Flags())
if err != nil { if err != nil {
@@ -52,7 +53,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
} }
context.Progress().Printf("Downloading & parsing package files...\n") 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 { if err != nil {
return fmt.Errorf("unable to update: %s", err) 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 // In separate goroutine (to avoid blocking main), push queue to downloader
go func() { go func() {
for _, task := range queue { 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 // 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-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures") cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)") 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)") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
return cmd return cmd
+19 -7
View File
@@ -2,21 +2,31 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/query" "github.com/smira/aptly/query"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
) )
func aptlyPackageSearch(cmd *commander.Command, args []string) error { func aptlyPackageSearch(cmd *commander.Command, args []string) error {
var err error var (
if len(args) != 1 { err error
q deb.PackageQuery
)
if len(args) > 1 {
cmd.Usage() cmd.Usage()
return commander.ErrCommandError return commander.ErrCommandError
} }
q, err := query.Parse(args[0]) if len(args) == 1 {
if err != nil { q, err = query.Parse(args[0])
return fmt.Errorf("unable to search: %s", err) if err != nil {
return fmt.Errorf("unable to search: %s", err)
}
} else {
q = &deb.MatchAllQuery{}
} }
result := q.Query(context.CollectionFactory().PackageCollection()) result := q.Query(context.CollectionFactory().PackageCollection())
@@ -33,10 +43,12 @@ func aptlyPackageSearch(cmd *commander.Command, args []string) error {
func makeCmdPackageSearch() *commander.Command { func makeCmdPackageSearch() *commander.Command {
cmd := &commander.Command{ cmd := &commander.Command{
Run: aptlyPackageSearch, Run: aptlyPackageSearch,
UsageLine: "search <package-query>", UsageLine: "search [<package-query>]",
Short: "search for packages matching query", Short: "search for packages matching query",
Long: ` 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: Example:
+1
View File
@@ -37,6 +37,7 @@ func makeCmdPublish() *commander.Command {
makeCmdPublishSnapshot(), makeCmdPublishSnapshot(),
makeCmdPublishSwitch(), makeCmdPublishSwitch(),
makeCmdPublishUpdate(), makeCmdPublishUpdate(),
makeCmdPublishShow(),
}, },
} }
} }
+80
View 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
}
+3 -1
View File
@@ -8,11 +8,13 @@ import (
func makeCmdRepoSearch() *commander.Command { func makeCmdRepoSearch() *commander.Command {
cmd := &commander.Command{ cmd := &commander.Command{
Run: aptlySnapshotMirrorRepoSearch, Run: aptlySnapshotMirrorRepoSearch,
UsageLine: "search <name> <package-query>", UsageLine: "search <name> [<package-query>]",
Short: "search repo for packages matching query", Short: "search repo for packages matching query",
Long: ` Long: `
Command search displays list of packages in local repository that match package query Command search displays list of packages in local repository that match package query
If query is not specified, all the packages are displayed.
Example: Example:
$ aptly repo search my-software '$Architecture (i386), Name (% *-dev)' $ aptly repo search my-software '$Architecture (i386), Name (% *-dev)'
+11
View File
@@ -22,6 +22,17 @@ func aptlyServe(cmd *commander.Command, args []string) error {
return commander.ErrCommandError 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 { if context.CollectionFactory().PublishedRepoCollection().Len() == 0 {
fmt.Printf("No published repositories, unable to serve.\n") fmt.Printf("No published repositories, unable to serve.\n")
return nil return nil
+18 -7
View File
@@ -2,16 +2,21 @@ package cmd
import ( import (
"fmt" "fmt"
"sort"
"github.com/smira/aptly/deb" "github.com/smira/aptly/deb"
"github.com/smira/aptly/query" "github.com/smira/aptly/query"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"sort"
) )
func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error { func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error {
var err error var (
if len(args) != 2 { err error
q deb.PackageQuery
)
if len(args) < 1 || len(args) > 2 {
cmd.Usage() cmd.Usage()
return commander.ErrCommandError return commander.ErrCommandError
} }
@@ -68,9 +73,13 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
list.PrepareIndex() list.PrepareIndex()
q, err := query.Parse(args[1]) if len(args) == 2 {
if err != nil { q, err = query.Parse(args[1])
return fmt.Errorf("unable to search: %s", err) if err != nil {
return fmt.Errorf("unable to search: %s", err)
}
} else {
q = &deb.MatchAllQuery{}
} }
withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool) 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 { func makeCmdSnapshotSearch() *commander.Command {
cmd := &commander.Command{ cmd := &commander.Command{
Run: aptlySnapshotMirrorRepoSearch, Run: aptlySnapshotMirrorRepoSearch,
UsageLine: "search <name> <package-query>", UsageLine: "search <name> [<package-query>]",
Short: "search snapshot for packages matching query", Short: "search snapshot for packages matching query",
Long: ` Long: `
Command search displays list of packages in snapshot that match package query Command search displays list of packages in snapshot that match package query
If query is not specified, all the packages are displayed.
Example: Example:
$ aptly snapshot search wheezy-main '$Architecture (i386), Name (% *-dev)' $ aptly snapshot search wheezy-main '$Architecture (i386), Name (% *-dev)'
+29
View File
@@ -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("Created At: %s\n", snapshot.CreatedAt.Format("2006-01-02 15:04:05 MST"))
fmt.Printf("Description: %s\n", snapshot.Description) fmt.Printf("Description: %s\n", snapshot.Description)
fmt.Printf("Number of packages: %d\n", snapshot.NumPackages()) 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) withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages { if withPackages {
+29 -13
View File
@@ -2,16 +2,20 @@ package deb
import ( import (
"archive/tar" "archive/tar"
"bufio"
"compress/bzip2" "compress/bzip2"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"github.com/mkrautz/goar"
"github.com/smira/aptly/utils"
"github.com/smira/go-xz"
"github.com/smira/lzma"
"io" "io"
"os" "os"
"strings" "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 // GetControlFileFromDeb reads control file from deb package
@@ -119,29 +123,41 @@ func GetContentsFromDeb(packageFile string) ([]string, error) {
} }
if strings.HasPrefix(header.Name, "data.tar") { 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 var tarInput io.Reader
switch header.Name { switch header.Name {
case "data.tar": case "data.tar":
tarInput = library tarInput = bufReader
case "data.tar.gz": case "data.tar.gz":
ungzip, err := gzip.NewReader(library) if isTar {
if err != nil { tarInput = bufReader
return nil, fmt.Errorf("unable to ungzip data.tar.gz from %s: %s", packageFile, err) } 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": case "data.tar.bz2":
tarInput = bzip2.NewReader(library) tarInput = bzip2.NewReader(bufReader)
case "data.tar.xz": case "data.tar.xz":
unxz, err := xz.NewReader(library) unxz, err := xz.NewReader(bufReader)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to unxz data.tar.xz from %s: %s", packageFile, err) return nil, fmt.Errorf("unable to unxz data.tar.xz from %s: %s", packageFile, err)
} }
defer unxz.Close() defer unxz.Close()
tarInput = unxz tarInput = unxz
case "data.tar.lzma": case "data.tar.lzma":
unlzma := lzma.NewReader(library) unlzma := lzma.NewReader(bufReader)
defer unlzma.Close() defer unlzma.Close()
tarInput = unlzma tarInput = unlzma
default: default:
+26 -9
View File
@@ -7,13 +7,28 @@ import (
) )
// BuildGraph generates graph contents from aptly object database // 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 var err error
graph := gographviz.NewEscape() graph := gographviz.NewEscape()
graph.SetDir(true) graph.SetDir(true)
graph.SetName("aptly") graph.SetName("aptly")
var labelStart string
var labelEnd string
switch layout {
case "vertical":
graph.AddAttr("aptly", "rankdir", "LR")
labelStart = ""
labelEnd = ""
case "horizontal":
fallthrough
default:
labelStart = "{"
labelEnd = "}"
}
existingNodes := map[string]bool{} existingNodes := map[string]bool{}
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error { err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error {
@@ -26,9 +41,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "darkgoldenrod1", "fillcolor": "darkgoldenrod1",
"label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}", "label": fmt.Sprintf("%sMirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d%s", labelStart, repo.Name, repo.ArchiveRoot,
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "), repo.Distribution, strings.Join(repo.Components, ", "),
strings.Join(repo.Architectures, ", "), repo.NumPackages()), strings.Join(repo.Architectures, ", "), repo.NumPackages(), labelEnd),
}) })
existingNodes[repo.UUID] = true existingNodes[repo.UUID] = true
return nil return nil
@@ -48,8 +63,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "mediumseagreen", "fillcolor": "mediumseagreen",
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}", "label": fmt.Sprintf("%sRepo %s|comment: %s|pkgs: %d%s", labelStart,
repo.Name, repo.Comment, repo.NumPackages()), repo.Name, repo.Comment, repo.NumPackages(), labelEnd),
}) })
existingNodes[repo.UUID] = true existingNodes[repo.UUID] = true
return nil return nil
@@ -79,7 +94,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "cadetblue1", "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" { if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
@@ -102,8 +118,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "darkolivegreen1", "fillcolor": "darkolivegreen1",
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, "label": fmt.Sprintf("%sPublished %s/%s|comp: %s|arch: %s%s", labelStart,
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")), repo.Prefix, repo.Distribution, strings.Join(repo.Components(), " "),
strings.Join(repo.Architectures, ", "), labelEnd),
}) })
for _, uuid := range repo.Sources { for _, uuid := range repo.Sources {
+15 -6
View File
@@ -5,11 +5,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "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" "io/ioutil"
"log" "log"
"os" "os"
@@ -18,6 +13,13 @@ import (
"strings" "strings"
"sync" "sync"
"time" "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 { type repoSourceItem struct {
@@ -677,7 +679,14 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
release["Components"] = strings.Join(p.Components(), " ") 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["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["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) release["SHA256"] += fmt.Sprintf(" %s %8d %s\n", info.SHA256, info.Size, path)
+23
View File
@@ -72,6 +72,9 @@ type DependencyQuery struct {
Dep Dependency Dep Dependency
} }
// MatchAllQuery is query that matches all the packages
type MatchAllQuery struct{}
// Matches if any of L, R matches // Matches if any of L, R matches
func (q *OrQuery) Matches(pkg PackageLike) bool { func (q *OrQuery) Matches(pkg PackageLike) bool {
return q.L.Matches(pkg) || q.R.Matches(pkg) 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 { func (q *PkgQuery) String() string {
return fmt.Sprintf("%s_%s_%s", q.Pkg, q.Version, q.Arch) 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 ""
}
+2 -2
View File
@@ -403,7 +403,7 @@ ok:
// DownloadPackageIndexes downloads & parses package index files // DownloadPackageIndexes downloads & parses package index files
func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory, func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory,
ignoreMismatch bool) error { ignoreMismatch bool, maxTries int) error {
if repo.packageList != nil { if repo.packageList != nil {
panic("packageList != nil") panic("packageList != nil")
} }
@@ -433,7 +433,7 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
for _, info := range packagesURLs { for _, info := range packagesURLs {
url, kind := info[0], info[1] 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 { if err != nil {
return err return err
} }
+4 -4
View File
@@ -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.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) 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(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true) 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.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) 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(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true) c.Assert(s.downloader.Empty(), Equals, true)
@@ -334,7 +334,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
err := s.flat.Fetch(downloader, nil) err := s.flat.Fetch(downloader, nil)
c.Assert(err, IsNil) 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(err, IsNil)
c.Assert(downloader.Empty(), Equals, true) c.Assert(downloader.Empty(), Equals, true)
@@ -367,7 +367,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
err := s.flat.Fetch(downloader, nil) err := s.flat.Fetch(downloader, nil)
c.Assert(err, IsNil) 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(err, IsNil)
c.Assert(downloader.Empty(), Equals, true) c.Assert(downloader.Empty(), Equals, true)
+46 -27
View File
@@ -52,6 +52,7 @@ type downloadTask struct {
result chan<- error result chan<- error
expected utils.ChecksumInfo expected utils.ChecksumInfo
ignoreMismatch bool ignoreMismatch bool
triesLeft int
} }
// NewDownloader creates new instance of Downloader which specified number // NewDownloader creates new instance of Downloader which specified number
@@ -127,13 +128,13 @@ func (downloader *downloaderImpl) GetProgress() aptly.Progress {
// Download starts new download task // Download starts new download task
func (downloader *downloaderImpl) Download(url string, destination string, result chan<- error) { 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 // DownloadWithChecksum starts new download task with checksum verification
func (downloader *downloaderImpl) DownloadWithChecksum(url string, destination string, result chan<- error, func (downloader *downloaderImpl) DownloadWithChecksum(url string, destination string, result chan<- error,
expected utils.ChecksumInfo, ignoreMismatch bool) { expected utils.ChecksumInfo, ignoreMismatch bool, maxTries int) {
downloader.queue <- &downloadTask{url: url, destination: destination, result: result, expected: expected, ignoreMismatch: ignoreMismatch} downloader.queue <- &downloadTask{url: url, destination: destination, result: result, expected: expected, ignoreMismatch: ignoreMismatch, triesLeft: maxTries}
} }
// handleTask processes single download task // handleTask processes single download task
@@ -153,32 +154,59 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
req.URL.RawQuery = "" 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 { 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) task.result <- fmt.Errorf("%s: %s", task.url, err)
return 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 { if resp.Body != nil {
defer resp.Body.Close() defer resp.Body.Close()
} }
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode < 200 || resp.StatusCode > 299 {
task.result <- &HTTPError{Code: resp.StatusCode, URL: task.url} return "", &HTTPError{Code: resp.StatusCode, URL: task.url}
return
} }
err = os.MkdirAll(filepath.Dir(task.destination), 0777) err = os.MkdirAll(filepath.Dir(task.destination), 0777)
if err != nil { if err != nil {
task.result <- fmt.Errorf("%s: %s", task.url, err) return "", fmt.Errorf("%s: %s", task.url, err)
return
} }
temppath := task.destination + ".down" temppath := task.destination + ".down"
outfile, err := os.Create(temppath) outfile, err := os.Create(temppath)
if err != nil { if err != nil {
task.result <- fmt.Errorf("%s: %s", task.url, err) return "", fmt.Errorf("%s: %s", task.url, err)
return
} }
defer outfile.Close() defer outfile.Close()
@@ -194,8 +222,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
_, err = io.Copy(w, resp.Body) _, err = io.Copy(w, resp.Body)
if err != nil { if err != nil {
os.Remove(temppath) os.Remove(temppath)
task.result <- fmt.Errorf("%s: %s", task.url, err) return "", fmt.Errorf("%s: %s", task.url, err)
return
} }
if task.expected.Size != -1 { if task.expected.Size != -1 {
@@ -218,20 +245,12 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
downloader.progress.Printf("WARNING: %s\n", err.Error()) downloader.progress.Printf("WARNING: %s\n", err.Error())
} else { } else {
os.Remove(temppath) os.Remove(temppath)
task.result <- err return "", err
return
} }
} }
} }
err = os.Rename(temppath, task.destination) return temppath, nil
if err != nil {
os.Remove(temppath)
task.result <- fmt.Errorf("%s: %s", task.url, err)
return
}
task.result <- nil
} }
// process implements download thread in goroutine // 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 // Temporary file would be already removed, so no need to cleanup
func DownloadTemp(downloader aptly.Downloader, url string) (*os.File, error) { 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 // DownloadTempWithChecksum is a DownloadTemp with checksum verification
// //
// Temporary file would be already removed, so no need to cleanup // 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") tempdir, err := ioutil.TempDir(os.TempDir(), "aptly")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -274,7 +293,7 @@ func DownloadTempWithChecksum(downloader aptly.Downloader, url string, expected
} }
ch := make(chan error, 1) ch := make(chan error, 1)
downloader.DownloadWithChecksum(url, tempfile, ch, expected, ignoreMismatch) downloader.DownloadWithChecksum(url, tempfile, ch, expected, ignoreMismatch, maxTries)
err = <-ch err = <-ch
@@ -311,7 +330,7 @@ var compressionMethods = []struct {
// DownloadTryCompression tries to download from URL .bz2, .gz and raw extension until // DownloadTryCompression tries to download from URL .bz2, .gz and raw extension until
// it finds existing file. // 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 var err error
for _, method := range compressionMethods { for _, method := range compressionMethods {
@@ -322,7 +341,7 @@ func DownloadTryCompression(downloader aptly.Downloader, url string, expectedChe
for suffix, expected := range expectedChecksums { for suffix, expected := range expectedChecksums {
if strings.HasSuffix(tryURL, suffix) { if strings.HasSuffix(tryURL, suffix) {
file, err = DownloadTempWithChecksum(downloader, tryURL, expected, ignoreMismatch) file, err = DownloadTempWithChecksum(downloader, tryURL, expected, ignoreMismatch, maxTries)
foundChecksum = true foundChecksum = true
break break
} }
+17 -17
View File
@@ -95,38 +95,38 @@ func (s *DownloaderSuite) TestDownloadWithChecksum(c *C) {
defer d.Shutdown() defer d.Shutdown()
ch := make(chan error) 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 res := <-ch
c.Assert(res, ErrorMatches, ".*size check mismatch 12 != 0") 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 res = <-ch
c.Assert(res, ErrorMatches, ".*md5 hash mismatch \"a1acb0fe91c7db45ec4d775192ec5738\" != \"abcdef\"") 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 res = <-ch
c.Assert(res, IsNil) 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 res = <-ch
c.Assert(res, IsNil) 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 res = <-ch
c.Assert(res, ErrorMatches, ".*sha1 hash mismatch \"921893bae6ad6fd818401875d6779254ef0ff0ec\" != \"abcdef\"") c.Assert(res, ErrorMatches, ".*sha1 hash mismatch \"921893bae6ad6fd818401875d6779254ef0ff0ec\" != \"abcdef\"")
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738", d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738",
SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec"}, false) SHA1: "921893bae6ad6fd818401875d6779254ef0ff0ec"}, false, 1)
res = <-ch res = <-ch
c.Assert(res, IsNil) c.Assert(res, IsNil)
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738", 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 res = <-ch
c.Assert(res, ErrorMatches, ".*sha256 hash mismatch \"b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac\" != \"abcdef\"") c.Assert(res, ErrorMatches, ".*sha256 hash mismatch \"b3c92ee1246176ed35f6e8463cd49074f29442f5bbffc3f8591cde1dcc849dac\" != \"abcdef\"")
d.DownloadWithChecksum(s.url+"/test", s.tempfile.Name(), ch, utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738", 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 res = <-ch
c.Assert(res, IsNil) c.Assert(res, IsNil)
} }
@@ -183,11 +183,11 @@ func (s *DownloaderSuite) TestDownloadTempWithChecksum(c *C) {
defer d.Shutdown() defer d.Shutdown()
f, err := DownloadTempWithChecksum(d, s.url+"/test", utils.ChecksumInfo{Size: 12, MD5: "a1acb0fe91c7db45ec4d775192ec5738", 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() defer f.Close()
c.Assert(err, IsNil) 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") c.Assert(err, ErrorMatches, ".*size check mismatch 12 != 13")
} }
@@ -220,7 +220,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
buf = make([]byte, 4) buf = make([]byte, 4)
d := NewFakeDownloader() d := NewFakeDownloader()
d.ExpectResponse("http://example.com/file.bz2", bzipData) 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) c.Assert(err, IsNil)
defer file.Close() defer file.Close()
io.ReadFull(r, buf) io.ReadFull(r, buf)
@@ -232,7 +232,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file.gz", gzipData) 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) c.Assert(err, IsNil)
defer file.Close() defer file.Close()
io.ReadFull(r, buf) 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.bz2", &HTTPError{Code: 404})
d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404}) d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file", rawData) 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) c.Assert(err, IsNil)
defer file.Close() defer file.Close()
io.ReadFull(r, buf) io.ReadFull(r, buf)
@@ -257,21 +257,21 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file.gz", "x") 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(err, ErrorMatches, "unexpected EOF")
c.Assert(d.Empty(), Equals, true) c.Assert(d.Empty(), Equals, true)
} }
func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) { func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) {
d := NewFakeDownloader() 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.*") c.Assert(err, ErrorMatches, "unexpected request.*")
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) 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.gz", &HTTPError{Code: 404})
d.ExpectError("http://example.com/file", errors.New("403")) 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") c.Assert(err, ErrorMatches, "403")
d = NewFakeDownloader() d = NewFakeDownloader()
@@ -283,6 +283,6 @@ func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) {
"file.gz": {Size: 7}, "file.gz": {Size: 7},
"file": {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.*") c.Assert(err, ErrorMatches, "checksums don't match.*")
} }
+2 -2
View File
@@ -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 // 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 var expectation expectedRequest
if len(f.expected) > 0 && f.expected[0].URL == url { if len(f.expected) > 0 && f.expected[0].URL == url {
expectation, f.expected = f.expected[0], f.expected[1:] 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 // Download performs fake download by matching against first expectation in the queue
func (f *FakeDownloader) Download(url string, filename string, result chan<- error) { 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 // Shutdown does nothing
+11
View 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
+55 -7
View File
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "APTLY" "1" "November 2016" "" "" .TH "APTLY" "1" "February 2017" "" ""
. .
.SH "NAME" .SH "NAME"
\fBaptly\fR \- Debian repository management tool \fBaptly\fR \- Debian repository management tool
@@ -506,6 +506,10 @@ disable verification of Release file signatures
\-\fBkeyring\fR= \-\fBkeyring\fR=
gpg keyring to use when verifying Release file (could be specified multiple times) 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" .SH "RENAMES MIRROR"
\fBaptly\fR \fBmirror\fR \fBrename\fR \fIold\-name\fR \fInew\-name\fR \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) download \.udeb packages (Debian installer support)
. .
.SH "SEARCH MIRROR FOR PACKAGES MATCHING QUERY" .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 .P
Command search displays list of packages in mirror that match package query Command search displays list of packages in mirror that match package query
. .
.P .P
If query is not specified, all the packages are displayed\.
.
.P
Example: Example:
. .
.IP "" 4 .IP "" 4
@@ -819,12 +826,15 @@ Example:
$ aptly repo rename wheezy\-min wheezy\-main $ aptly repo rename wheezy\-min wheezy\-main
. .
.SH "SEARCH REPO FOR PACKAGES MATCHING QUERY" .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 .P
Command search displays list of packages in local repository that match package query Command search displays list of packages in local repository that match package query
. .
.P .P
If query is not specified, all the packages are displayed\.
.
.P
Example: Example:
. .
.IP "" 4 .IP "" 4
@@ -1113,12 +1123,15 @@ Example:
$ aptly snapshot rename wheezy\-min wheezy\-main $ aptly snapshot rename wheezy\-min wheezy\-main
. .
.SH "SEARCH SNAPSHOT FOR PACKAGES MATCHING QUERY" .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 .P
Command search displays list of packages in snapshot that match package query Command search displays list of packages in snapshot that match package query
. .
.P .P
If query is not specified, all the packages are displayed\.
.
.P
Example: Example:
. .
.IP "" 4 .IP "" 4
@@ -1536,11 +1549,33 @@ don\(cqt generate Contents indexes
\-\fBskip\-signing\fR=false \-\fBskip\-signing\fR=false
don\(cqt sign Release files with GPG don\(cqt sign Release files with GPG
. .
.SH "SEARCH FOR PACKAGES MATCHING QUERY" .SH "SHOWS DETAILS OF PUBLISHED REPOSITORY"
\fBaptly\fR \fBpackage\fR \fBsearch\fR \fIpackage\-query\fR \fBaptly\fR \fBpublish\fR \fBshow\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR]
. .
.P .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 .P
Example: Example:
@@ -1689,6 +1724,10 @@ Options:
render graph to specified format (png, svg, pdf, etc\.) render graph to specified format (png, svg, pdf, etc\.)
. .
.TP .TP
\-\fBlayout\fR=horizontal
create a more \(cqvertical\(cq or a more \(cqhorizontal\(cq graph layout
.
.TP
\-\fBoutput\fR= \-\fBoutput\fR=
specify output filename, default is to open result in viewer specify output filename, default is to open result in viewer
. .
@@ -1834,5 +1873,14 @@ Benoit Foucher (https://github\.com/bentoi)
.IP "\[ci]" 4 .IP "\[ci]" 4
Geoffrey Thomas (https://github\.com/geofft) 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 .IP "" 0
+8
View File
@@ -270,6 +270,14 @@ class BaseTest(object):
if a != b: if a != b:
self.verify_match(a, b, match_prepare=pprint.pformat) 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): def check_in(self, item, l):
if not item in l: if not item in l:
raise Exception("item %r not in %r", item, l) raise Exception("item %r not in %r", item, l)
File diff suppressed because it is too large Load Diff
+9
View File
@@ -43,3 +43,12 @@ class SearchMirror5Test(BaseTest):
fixtureDB = True fixtureDB = True
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
runCmd = "aptly mirror search -format='{{.Package}}#{{.Version}}' wheezy-main '$$Architecture (i386), Name (% *-dev)'" 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 Created At: 2014-06-29 01:43:01 MSK
Description: Snapshot from mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy Description: Snapshot from mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy
Number of packages: 56121 Number of packages: 56121
Sources:
wheezy-main [repo]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -1,6 +1,8 @@
Name: snap6 Name: snap6
Description: Snapshot from local repo [local-repo] Description: Snapshot from local repo [local-repo]
Number of packages: 3 Number of packages: 3
Sources:
local-repo [local]
Packages: Packages:
libboost-program-options-dev_1.49.0.1_i386 libboost-program-options-dev_1.49.0.1_i386
pyspi_0.6.1-1.3_source pyspi_0.6.1-1.3_source
@@ -1,6 +1,8 @@
Name: snap2 Name: snap2
Description: Filtered 'snap1', query was: 'mame unrar' Description: Filtered 'snap1', query was: 'mame unrar'
Number of packages: 4 Number of packages: 4
Sources:
snap1 [snapshot]
Packages: Packages:
mame_0.146-5_amd64 mame_0.146-5_amd64
unrar_1:4.1.4-1_amd64 unrar_1:4.1.4-1_amd64
@@ -1,6 +1,8 @@
Name: snap2 Name: snap2
Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4)' Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4)'
Number of packages: 9 Number of packages: 9
Sources:
snap1 [snapshot]
Packages: Packages:
init-system-helpers_1.18~bpo70+1_all init-system-helpers_1.18~bpo70+1_all
libestr0_0.1.9-1~bpo70+1_amd64 libestr0_0.1.9-1~bpo70+1_amd64
@@ -1,6 +1,8 @@
Name: snap2 Name: snap2
Description: Filtered 'snap1', query was: 'Priority (required) nginx xyz' Description: Filtered 'snap1', query was: 'Priority (required) nginx xyz'
Number of packages: 123 Number of packages: 123
Sources:
snap1 [snapshot]
Packages: Packages:
debconf_1.5.49_all debconf_1.5.49_all
debconf-i18n_1.5.49_all debconf-i18n_1.5.49_all
@@ -1,6 +1,8 @@
Name: snap2 Name: snap2
Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4), $Architecture (i386)' Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4), $Architecture (i386)'
Number of packages: 10 Number of packages: 10
Sources:
snap1 [snapshot]
Packages: Packages:
init-system-helpers_1.18~bpo70+1_all init-system-helpers_1.18~bpo70+1_all
libestr0_0.1.9-1~bpo70+1_i386 libestr0_0.1.9-1~bpo70+1_i386
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 02:23:49 MSK Created At: 2014-06-29 02:23:49 MSK
Description: Merged from sources: 'snap1', 'snap2' Description: Merged from sources: 'snap1', 'snap2'
Number of packages: 56782 Number of packages: 56782
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,10 @@ Name: snap4
Created At: 2014-06-29 02:14:26 MSK Created At: 2014-06-29 02:14:26 MSK
Description: Merged from sources: 'snap1', 'snap2', 'snap3' Description: Merged from sources: 'snap1', 'snap2', 'snap3'
Number of packages: 58968 Number of packages: 58968
Sources:
snap1 [snapshot]
snap2 [snapshot]
snap3 [snapshot]
Packages: Packages:
0ad-data_0.0.16-1~bpo70+1_all 0ad-data_0.0.16-1~bpo70+1_all
0ad-data-common_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 Created At: 2014-06-29 02:15:01 MSK
Description: Merged from sources: 'snap1', 'snap2', 'snap3' Description: Merged from sources: 'snap1', 'snap2', 'snap3'
Number of packages: 58802 Number of packages: 58802
Sources:
snap1 [snapshot]
snap2 [snapshot]
snap3 [snapshot]
Packages: Packages:
0ad-data_0.0.16-1~bpo70+1_all 0ad-data_0.0.16-1~bpo70+1_all
0ad-data-common_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 Created At: 2014-06-29 02:16:12 MSK
Description: Merged from sources: 'snap3', 'snap2', 'snap1' Description: Merged from sources: 'snap3', 'snap2', 'snap1'
Number of packages: 58817 Number of packages: 58817
Sources:
snap3 [snapshot]
snap2 [snapshot]
snap1 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
0ad-data-common_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:19:14 MSK Created At: 2014-06-29 02:19:14 MSK
Description: Merged from sources: 'snap1', 'snap2', 'snap3' Description: Merged from sources: 'snap1', 'snap2', 'snap3'
Number of packages: 61524 Number of packages: 61524
Sources:
snap1 [snapshot]
snap2 [snapshot]
snap3 [snapshot]
Packages: Packages:
0ad-data_0.0.16-1~bpo70+1_all 0ad-data_0.0.16-1~bpo70+1_all
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 02:00:49 MSK Created At: 2014-06-29 02:00:49 MSK
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
Number of packages: 73295 Number of packages: 73295
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 02:03:59 MSK Created At: 2014-06-29 02:03:59 MSK
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
Number of packages: 56130 Number of packages: 56130
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 02:31:20 MSK Created At: 2014-06-29 02:31:20 MSK
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'mame unrar' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'mame unrar'
Number of packages: 56125 Number of packages: 56125
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 01:50:10 MSK Created At: 2014-06-29 01:50:10 MSK
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
Number of packages: 56126 Number of packages: 56126
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 01:52:15 MSK Created At: 2014-06-29 01:52:15 MSK
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
Number of packages: 56121 Number of packages: 56121
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 01:57:44 MSK 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)' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'lunar-landing mars-landing (>= 1.0)'
Number of packages: 56121 Number of packages: 56121
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
@@ -2,6 +2,9 @@ Name: snap3
Created At: 2014-06-29 01:58:24 MSK Created At: 2014-06-29 01:58:24 MSK
Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)' Description: Pulled into 'snap1' with 'snap2' as source, pull request was: 'rsyslog (>= 7.4.4)'
Number of packages: 56131 Number of packages: 56131
Sources:
snap1 [snapshot]
snap2 [snapshot]
Packages: Packages:
0ad-data_0~r11863-1_all 0ad-data_0~r11863-1_all
2ping_2.0-1_all 2ping_2.0-1_all
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 Created At: 2014-06-28 03:01:02 MSK
Description: Snapshot from mirror [wheezy-non-free]: http://mirror.yandex.ru/debian/ wheezy Description: Snapshot from mirror [wheezy-non-free]: http://mirror.yandex.ru/debian/ wheezy
Number of packages: 661 Number of packages: 661
Sources:
wheezy-non-free [repo]
Packages: Packages:
abs-guide_6.5-1_all abs-guide_6.5-1_all
album_4.06-2_all album_4.06-2_all
@@ -2,3 +2,5 @@ Name: snap1
Created At: 2014-01-24 13:06:43 MSK Created At: 2014-01-24 13:06:43 MSK
Description: Snapshot from mirror [wheezy-non-free]: http://mirror.yandex.ru/debian/ wheezy Description: Snapshot from mirror [wheezy-non-free]: http://mirror.yandex.ru/debian/ wheezy
Number of packages: 661 Number of packages: 661
Sources:
wheezy-non-free [repo]
+9
View File
@@ -57,3 +57,12 @@ class SearchSnapshot6Test(BaseTest):
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"] fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"]
runCmd = "aptly snapshot search -format='{{.Package}}#{{.Version}}' wheezy-main '$$Architecture (i386), Name (% *-dev)'" 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
View File
@@ -0,0 +1,5 @@
Prefix: .
Distribution: maverick
Architectures: amd64 i386
Sources:
main: snap1 [snapshot]
+5
View File
@@ -0,0 +1,5 @@
Prefix: ppa/smira
Distribution: maverick
Architectures: amd64 i386
Sources:
main: snap1 [snapshot]
+1
View File
@@ -3,6 +3,7 @@ Testing publishing snapshots
""" """
from .drop import * from .drop import *
from .show import *
from .list import * from .list import *
from .repo import * from .repo import *
from .snapshot import * from .snapshot import *
+27
View 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"
@@ -0,0 +1 @@
ERROR: '/root' is inaccessible, check access rights
+15
View File
@@ -8,9 +8,24 @@ import signal
import subprocess import subprocess
import shlex import shlex
import time import time
import errno
from lib import BaseTest 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): class Serve1Test(BaseTest):
""" """
File diff suppressed because it is too large Load Diff
+9
View File
@@ -47,3 +47,12 @@ class SearchRepo5Test(BaseTest):
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
fixtureCmds = ["aptly repo create wheezy-main", "aptly repo import wheezy-main wheezy-main Name"] 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)'" 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"
File diff suppressed because it is too large Load Diff
+8
View File
@@ -42,3 +42,11 @@ class SearchPackage5Test(BaseTest):
fixtureDB = True fixtureDB = True
outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n")))
runCmd = "aptly package search -format='{{.Package}}#{{.Version}}' '$$Architecture (i386), Name (% *-dev)'" 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"
+30
View File
@@ -1,4 +1,5 @@
from api_lib import APITest from api_lib import APITest
import xml.etree.ElementTree as ET
class GraphAPITest(APITest): class GraphAPITest(APITest):
@@ -15,3 +16,32 @@ class GraphAPITest(APITest):
resp = self.get("/api/graph.svg") resp = self.get("/api/graph.svg")
self.check_equal(resp.headers["Content-Type"], "image/svg+xml") self.check_equal(resp.headers["Content-Type"], "image/svg+xml")
self.check_equal(resp.content[:4], '<?xm') 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)
+1 -3
View File
@@ -30,15 +30,13 @@ func (s *CompressSuite) TestCompress(c *C) {
err := CompressFile(s.tempfile, false) err := CompressFile(s.tempfile, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
buf := make([]byte, len(testString))
file, err := os.Open(s.tempfile.Name() + ".gz") file, err := os.Open(s.tempfile.Name() + ".gz")
c.Assert(err, IsNil) c.Assert(err, IsNil)
gzReader, err := gzip.NewReader(file) gzReader, err := gzip.NewReader(file)
c.Assert(err, IsNil) c.Assert(err, IsNil)
_, err = gzReader.Read(buf) buf, err := ioutil.ReadAll(gzReader)
c.Assert(err, IsNil) c.Assert(err, IsNil)
gzReader.Close() gzReader.Close()
+21
View File
@@ -1,2 +1,23 @@
// Package utils collects various services: simple operations, compression, etc. // Package utils collects various services: simple operations, compression, etc.
package utils 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
}