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
.github/ISSUE_TEMPLATE.md vendored Normal file
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
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
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`

8
.gitignore vendored
View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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
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/

View File

@@ -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'

View File

@@ -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

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/>`_.
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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)'

View File

@@ -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

View File

@@ -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:

View File

@@ -37,6 +37,7 @@ func makeCmdPublish() *commander.Command {
makeCmdPublishSnapshot(),
makeCmdPublishSwitch(),
makeCmdPublishUpdate(),
makeCmdPublishShow(),
},
}
}

80
cmd/publish_show.go Normal file
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
}

View File

@@ -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)'

View File

@@ -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

View File

@@ -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)'

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("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 {

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 ""
}

View File

@@ -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
}

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.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)

View File

@@ -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
}

View File

@@ -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.*")
}

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
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
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

View File

@@ -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

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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]

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
Prefix: .
Distribution: maverick
Architectures: amd64 i386
Sources:
main: snap1 [snapshot]

View File

@@ -0,0 +1,5 @@
Prefix: ppa/smira
Distribution: maverick
Architectures: amd64 i386
Sources:
main: snap1 [snapshot]

View File

@@ -3,6 +3,7 @@ Testing publishing snapshots
"""
from .drop import *
from .show import *
from .list import *
from .repo import *
from .snapshot import *

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"

View File

@@ -0,0 +1 @@
ERROR: '/root' is inaccessible, check access rights

View File

@@ -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):
"""

File diff suppressed because it is too large Load Diff

View File

@@ -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"

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
}