mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-11-12 14:31:02 +00:00
This patch adds validation to the version pasing logic (parseVersion) to not allow packages to be registered which apt wouldn't accept anyway. It then propogates validation errors through CompareVersions, the way in which parseVersion gets called, so that appropriate decisions can be made. New packages with invalid versions are not accepted. For any pre-exisiting packages with invalid versions, as much care is taken as possible to log warnings, and not use those packages. This PR is loosely bnased on one by by Aaron Foster <afoster@cloudflare.com> though it has largely been written at this point. Signed off with internal and external emails for clarity, but commit is under personal email where we typically do open source contributions from. Signed-off-by: Phil Dibowitz <pdibowitz@cloudflare.com> Signed-off-by: Phil Dibowitz <pdibowitz@ipom.com>
793 lines
21 KiB
Go
793 lines
21 KiB
Go
package deb
|
|
|
|
import (
|
|
gocontext "context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/aptly-dev/aptly/aptly"
|
|
"github.com/aptly-dev/aptly/utils"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// Package is single instance of Debian package
|
|
type Package struct {
|
|
// Basic package properties
|
|
Name string
|
|
Version string
|
|
Architecture string
|
|
// If this source package, this field holds "real" architecture value,
|
|
// while Architecture would be equal to "source"
|
|
SourceArchitecture string
|
|
// For binary package, name of source package
|
|
Source string
|
|
// List of virtual packages this package provides
|
|
Provides []string
|
|
// Hash of files section
|
|
FilesHash uint64
|
|
// Is this package a dummy installer package
|
|
IsInstaller bool
|
|
// Is this source package
|
|
IsSource bool
|
|
// Is this udeb package
|
|
IsUdeb bool
|
|
// Is this >= 0.6 package?
|
|
V06Plus bool
|
|
// Offload fields
|
|
deps *PackageDependencies
|
|
extra *Stanza
|
|
files *PackageFiles
|
|
contents []string
|
|
// Mother collection
|
|
collection *PackageCollection
|
|
}
|
|
|
|
// Package types
|
|
const (
|
|
PackageTypeBinary = "deb"
|
|
PackageTypeUdeb = "udeb"
|
|
PackageTypeSource = "source"
|
|
PackageTypeInstaller = "installer"
|
|
)
|
|
|
|
// Special architectures
|
|
const (
|
|
ArchitectureAll = "all"
|
|
ArchitectureAny = "any"
|
|
ArchitectureSource = "source"
|
|
)
|
|
|
|
// Check interface
|
|
var (
|
|
_ json.Marshaler = &Package{}
|
|
)
|
|
|
|
// NewPackageFromControlFile creates Package from parsed Debian control file
|
|
func NewPackageFromControlFile(input Stanza) *Package {
|
|
result := &Package{
|
|
Name: input["Package"],
|
|
Version: input["Version"],
|
|
Architecture: input["Architecture"],
|
|
Source: input["Source"],
|
|
V06Plus: true,
|
|
}
|
|
|
|
delete(input, "Package")
|
|
delete(input, "Version")
|
|
delete(input, "Architecture")
|
|
delete(input, "Source")
|
|
|
|
filesize, _ := strconv.ParseInt(input["Size"], 10, 64)
|
|
|
|
md5, ok := input["MD5sum"]
|
|
if !ok {
|
|
// there are some broken repos out there with MD5 in wrong field
|
|
md5 = input["MD5Sum"]
|
|
}
|
|
|
|
result.UpdateFiles(PackageFiles{PackageFile{
|
|
Filename: filepath.Base(input["Filename"]),
|
|
downloadPath: filepath.Dir(input["Filename"]),
|
|
Checksums: utils.ChecksumInfo{
|
|
Size: filesize,
|
|
MD5: strings.TrimSpace(md5),
|
|
SHA1: strings.TrimSpace(input["SHA1"]),
|
|
SHA256: strings.TrimSpace(input["SHA256"]),
|
|
SHA512: strings.TrimSpace(input["SHA512"]),
|
|
},
|
|
}})
|
|
|
|
delete(input, "Filename")
|
|
delete(input, "MD5sum")
|
|
delete(input, "MD5Sum")
|
|
delete(input, "SHA1")
|
|
delete(input, "SHA256")
|
|
delete(input, "SHA512")
|
|
delete(input, "Size")
|
|
|
|
depends := &PackageDependencies{}
|
|
depends.Depends = parseDependencies(input, "Depends")
|
|
depends.PreDepends = parseDependencies(input, "Pre-Depends")
|
|
depends.Suggests = parseDependencies(input, "Suggests")
|
|
depends.Recommends = parseDependencies(input, "Recommends")
|
|
result.deps = depends
|
|
|
|
result.Provides = parseDependencies(input, "Provides")
|
|
|
|
result.extra = &input
|
|
|
|
return result
|
|
}
|
|
|
|
// NewSourcePackageFromControlFile creates Package from parsed Debian control file for source package
|
|
func NewSourcePackageFromControlFile(input Stanza) (*Package, error) {
|
|
result := &Package{
|
|
IsSource: true,
|
|
Name: input["Package"],
|
|
Version: input["Version"],
|
|
Architecture: "source",
|
|
SourceArchitecture: input["Architecture"],
|
|
V06Plus: true,
|
|
}
|
|
|
|
delete(input, "Package")
|
|
delete(input, "Version")
|
|
delete(input, "Architecture")
|
|
|
|
var err error
|
|
|
|
files := make(PackageFiles, 0, 3)
|
|
files, err = files.ParseSumFields(input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(input, "Files")
|
|
delete(input, "Checksums-Sha1")
|
|
delete(input, "Checksums-Sha256")
|
|
|
|
for i := range files {
|
|
files[i].downloadPath = input["Directory"]
|
|
}
|
|
|
|
result.UpdateFiles(files)
|
|
|
|
depends := &PackageDependencies{}
|
|
depends.BuildDepends = parseDependencies(input, "Build-Depends")
|
|
depends.BuildDependsInDep = parseDependencies(input, "Build-Depends-Indep")
|
|
result.deps = depends
|
|
|
|
result.extra = &input
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// NewUdebPackageFromControlFile creates .udeb Package from parsed Debian control file
|
|
func NewUdebPackageFromControlFile(input Stanza) *Package {
|
|
p := NewPackageFromControlFile(input)
|
|
p.IsUdeb = true
|
|
|
|
return p
|
|
}
|
|
|
|
// NewInstallerPackageFromControlFile creates a dummy installer Package from parsed hash sum file
|
|
func NewInstallerPackageFromControlFile(input Stanza, repo *RemoteRepo, component, architecture string, d aptly.Downloader) (*Package, error) {
|
|
p := &Package{
|
|
Name: "installer",
|
|
Architecture: architecture,
|
|
IsInstaller: true,
|
|
V06Plus: true,
|
|
extra: &Stanza{},
|
|
deps: &PackageDependencies{},
|
|
}
|
|
|
|
files := make(PackageFiles, 0)
|
|
files, err := files.ParseSumField(input[""], func(sum *utils.ChecksumInfo, data string) { sum.SHA256 = data }, false, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var relPath string
|
|
if repo.Distribution == aptly.DistributionFocal {
|
|
relPath = filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "legacy-images")
|
|
} else {
|
|
relPath = filepath.Join("dists", repo.Distribution, component, fmt.Sprintf("%s-%s", p.Name, architecture), "current", "images")
|
|
}
|
|
for i := range files {
|
|
files[i].downloadPath = relPath
|
|
|
|
url := repo.PackageURL(files[i].DownloadURL()).String()
|
|
var size int64
|
|
size, err = d.GetLength(gocontext.TODO(), url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files[i].Checksums.Size = size
|
|
}
|
|
|
|
p.UpdateFiles(files)
|
|
return p, nil
|
|
}
|
|
|
|
// Key returns unique key identifying package
|
|
func (p *Package) Key(prefix string) []byte {
|
|
if p.V06Plus {
|
|
return []byte(fmt.Sprintf("%sP%s %s %s %08x", prefix, p.Architecture, p.Name, p.Version, p.FilesHash))
|
|
}
|
|
|
|
return p.ShortKey(prefix)
|
|
}
|
|
|
|
// ShortKey returns key for the package that should be unique in one list
|
|
func (p *Package) ShortKey(prefix string) []byte {
|
|
return []byte(fmt.Sprintf("%sP%s %s %s", prefix, p.Architecture, p.Name, p.Version))
|
|
}
|
|
|
|
// String creates readable representation
|
|
func (p *Package) String() string {
|
|
return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture)
|
|
}
|
|
|
|
// ExtendedStanza returns package stanza enhanced with aptly-specific fields
|
|
func (p *Package) ExtendedStanza() Stanza {
|
|
stanza := p.Stanza()
|
|
stanza["FilesHash"] = fmt.Sprintf("%08x", p.FilesHash)
|
|
stanza["Key"] = string(p.Key(""))
|
|
stanza["ShortKey"] = string(p.ShortKey(""))
|
|
|
|
return stanza
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaller interface
|
|
func (p *Package) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(p.ExtendedStanza())
|
|
}
|
|
|
|
// GetField returns fields from package
|
|
func (p *Package) GetField(name string) string {
|
|
switch name {
|
|
// $Version is handled in FieldQuery
|
|
case "$Source":
|
|
if p.IsSource {
|
|
return ""
|
|
}
|
|
source := p.Source
|
|
if source == "" {
|
|
return p.Name
|
|
} else if pos := strings.Index(source, "("); pos != -1 {
|
|
return strings.TrimSpace(source[:pos])
|
|
}
|
|
return source
|
|
case "$SourceVersion":
|
|
if p.IsSource {
|
|
return ""
|
|
}
|
|
source := p.Source
|
|
if pos := strings.Index(source, "("); pos != -1 {
|
|
if pos2 := strings.LastIndex(source, ")"); pos2 != -1 && pos2 > pos {
|
|
return strings.TrimSpace(source[pos+1 : pos2])
|
|
}
|
|
}
|
|
return p.Version
|
|
case "$Architecture":
|
|
return p.Architecture
|
|
case "$PackageType":
|
|
if p.IsSource {
|
|
return PackageTypeSource
|
|
}
|
|
if p.IsUdeb {
|
|
return PackageTypeUdeb
|
|
}
|
|
return PackageTypeBinary
|
|
case "Name":
|
|
return p.Name
|
|
case "Version":
|
|
return p.Version
|
|
case "Architecture":
|
|
if p.IsSource {
|
|
return p.SourceArchitecture
|
|
}
|
|
return p.Architecture
|
|
case "Source":
|
|
return p.Source
|
|
case "Depends":
|
|
return strings.Join(p.Deps().Depends, ", ")
|
|
case "Pre-Depends":
|
|
return strings.Join(p.Deps().PreDepends, ", ")
|
|
case "Suggests":
|
|
return strings.Join(p.Deps().Suggests, ", ")
|
|
case "Recommends":
|
|
return strings.Join(p.Deps().Recommends, ", ")
|
|
case "Provides":
|
|
return strings.Join(p.Provides, ", ")
|
|
case "Build-Depends":
|
|
return strings.Join(p.Deps().BuildDepends, ", ")
|
|
case "Build-Depends-Indep":
|
|
return strings.Join(p.Deps().BuildDependsInDep, ", ")
|
|
default:
|
|
return p.Extra()[name]
|
|
}
|
|
}
|
|
|
|
// ProvidedPackages returns just the package names of the provided packages (without version numbers such as
|
|
// `(= 1.2.3)`).
|
|
func (p *Package) ProvidedPackages() []string {
|
|
result := make([]string, len(p.Provides))
|
|
for i, provided := range p.Provides {
|
|
providedDep, err := ParseDependency(provided)
|
|
if err != nil {
|
|
// Should never happen, but I included this, so it definitely has the old behavior in case there is no
|
|
// special syntax.
|
|
result[i] = provided
|
|
} else {
|
|
result[i] = providedDep.Pkg
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// MatchesArchitecture checks whether packages matches specified architecture
|
|
func (p *Package) MatchesArchitecture(arch string) bool {
|
|
if p.Architecture == ArchitectureAll && arch != ArchitectureSource {
|
|
return true
|
|
}
|
|
|
|
return p.Architecture == arch
|
|
}
|
|
|
|
func JoinErrors(errs ...error) error {
|
|
var combinedErr error
|
|
for _, err := range errs {
|
|
if err != nil {
|
|
if combinedErr == nil {
|
|
combinedErr = err
|
|
} else {
|
|
combinedErr = fmt.Errorf("%w\n%v", combinedErr, err)
|
|
}
|
|
}
|
|
}
|
|
return combinedErr
|
|
}
|
|
|
|
// providesDependency checks if the package `Provide:`s the dependency, assuming that the architecture matches.
|
|
// If the `Provides:` entry includes a version number, it will be considered when checking the dependency.
|
|
func (p *Package) providesDependency(dep Dependency) (bool, error) {
|
|
var errs []error // won't cause an allocation in case of no errors
|
|
for _, provided := range p.Provides {
|
|
providedDep, err := ParseDependency(provided)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
if providedDep.Relation != VersionEqual && providedDep.Relation != VersionDontCare {
|
|
// The only relation allowed here is `=`.
|
|
// > The relations allowed are [...]. The exception is the Provides field, for which only = is allowed.
|
|
// > [...]
|
|
// > A Provides field may contain version numbers, and such a version number will be considered when
|
|
// > considering a dependency on or conflict with the virtual package name.
|
|
// -- https://www.debian.org/doc/debian-policy/ch-relationships.html
|
|
errs = append(errs, fmt.Errorf("unsupported relation in Provides: %s", providedDep.String()))
|
|
continue
|
|
}
|
|
providedVersion := providedDep.Version
|
|
if providedVersion == "" {
|
|
providedVersion = p.Version
|
|
}
|
|
if providedDep.Pkg == dep.Pkg && versionSatisfiesDependency(providedVersion, dep) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, JoinErrors(errs...)
|
|
}
|
|
|
|
// MatchesDependency checks whether package matches specified dependency
|
|
func (p *Package) MatchesDependency(dep Dependency) bool {
|
|
if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) {
|
|
return false
|
|
}
|
|
|
|
providesDep, err := p.providesDependency(dep)
|
|
if err != nil {
|
|
log.Warn().Err(err).Msg("Error while checking if package provides dependency")
|
|
}
|
|
if providesDep {
|
|
return true
|
|
}
|
|
|
|
if dep.Pkg != p.Name {
|
|
return false
|
|
}
|
|
|
|
return versionSatisfiesDependency(p.Version, dep)
|
|
}
|
|
|
|
func versionSatisfiesDependency(version string, dep Dependency) bool {
|
|
r, err := CompareVersions(version, dep.Version)
|
|
|
|
switch dep.Relation {
|
|
case VersionEqual:
|
|
return err == nil && r == 0
|
|
case VersionLess:
|
|
return err == nil && r < 0
|
|
case VersionGreater:
|
|
return err == nil && r > 0
|
|
case VersionLessOrEqual:
|
|
return err == nil && r <= 0
|
|
case VersionGreaterOrEqual:
|
|
return err == nil && r >= 0
|
|
case VersionPatternMatch:
|
|
matched, err := filepath.Match(dep.Version, version)
|
|
return err == nil && matched
|
|
case VersionRegexp:
|
|
return dep.Regexp.FindStringIndex(version) != nil
|
|
case VersionDontCare:
|
|
return true
|
|
default:
|
|
panic(fmt.Sprintf("unknown relation: %d", dep.Relation))
|
|
}
|
|
}
|
|
|
|
// GetName returns package name
|
|
func (p *Package) GetName() string {
|
|
return p.Name
|
|
}
|
|
|
|
// GetFullName returns the package full name
|
|
func (p *Package) GetFullName() string {
|
|
return strings.Join([]string{p.Name, p.Version, p.Architecture}, "_")
|
|
}
|
|
|
|
// GetVersion returns package version
|
|
func (p *Package) GetVersion() string {
|
|
return p.Version
|
|
}
|
|
|
|
// GetArchitecture returns package arch
|
|
func (p *Package) GetArchitecture() string {
|
|
return p.Architecture
|
|
}
|
|
|
|
// GetDependencies compiles list of dependencies by flags from options
|
|
func (p *Package) GetDependencies(options int) (dependencies []string) {
|
|
deps := p.Deps()
|
|
|
|
dependencies = make([]string, 0, 30)
|
|
dependencies = append(dependencies, deps.Depends...)
|
|
dependencies = append(dependencies, deps.PreDepends...)
|
|
|
|
if options&DepFollowRecommends == DepFollowRecommends {
|
|
dependencies = append(dependencies, deps.Recommends...)
|
|
}
|
|
|
|
if options&DepFollowSuggests == DepFollowSuggests {
|
|
dependencies = append(dependencies, deps.Suggests...)
|
|
}
|
|
|
|
if options&DepFollowBuild == DepFollowBuild {
|
|
dependencies = append(dependencies, deps.BuildDepends...)
|
|
dependencies = append(dependencies, deps.BuildDependsInDep...)
|
|
}
|
|
|
|
if options&DepFollowSource == DepFollowSource {
|
|
source := p.Source
|
|
if source == "" {
|
|
source = p.Name
|
|
}
|
|
if strings.Contains(source, ")") {
|
|
dependencies = append(dependencies, fmt.Sprintf("%s {source}", source))
|
|
} else {
|
|
dependencies = append(dependencies, fmt.Sprintf("%s (= %s) {source}", source, p.Version))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// QualifiedName returns [$SECTION/]$NAME
|
|
func (p *Package) QualifiedName() string {
|
|
section := p.Extra()["Section"]
|
|
if section != "" {
|
|
return section + "/" + p.Name
|
|
}
|
|
|
|
return p.Name
|
|
}
|
|
|
|
// Extra returns Stanza of extra fields (it may load it from collection)
|
|
func (p *Package) Extra() Stanza {
|
|
if p.extra == nil {
|
|
if p.collection == nil {
|
|
panic("extra == nil && collection == nil")
|
|
}
|
|
p.extra = p.collection.loadExtra(p)
|
|
}
|
|
|
|
return *p.extra
|
|
}
|
|
|
|
// Deps returns parsed package dependencies (it may load it from collection)
|
|
func (p *Package) Deps() *PackageDependencies {
|
|
if p.deps == nil {
|
|
if p.collection == nil {
|
|
panic("deps == nil && collection == nil")
|
|
}
|
|
|
|
p.deps = p.collection.loadDependencies(p)
|
|
}
|
|
|
|
return p.deps
|
|
}
|
|
|
|
// Files returns parsed files records (it may load it from collection)
|
|
func (p *Package) Files() PackageFiles {
|
|
if p.files == nil {
|
|
if p.collection == nil {
|
|
panic("files == nil && collection == nil")
|
|
}
|
|
|
|
p.files = p.collection.loadFiles(p)
|
|
}
|
|
|
|
return *p.files
|
|
}
|
|
|
|
// Contents returns cached package contents
|
|
func (p *Package) Contents(packagePool aptly.PackagePool, progress aptly.Progress) []string {
|
|
if p.IsSource {
|
|
return nil
|
|
}
|
|
|
|
return p.collection.loadContents(p, packagePool, progress)
|
|
}
|
|
|
|
// CalculateContents looks up contents in package file
|
|
func (p *Package) CalculateContents(packagePool aptly.PackagePool, progress aptly.Progress) ([]string, error) {
|
|
if p.IsSource {
|
|
return nil, nil
|
|
}
|
|
|
|
file := p.Files()[0]
|
|
poolPath, err := file.GetPoolPath(packagePool)
|
|
if err != nil {
|
|
if progress != nil {
|
|
progress.ColoredPrintf("@y[!]@| @!Failed to build pool path: @| %s", err)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
reader, err := packagePool.Open(poolPath)
|
|
if err != nil {
|
|
if progress != nil {
|
|
progress.ColoredPrintf("@y[!]@| @!Failed to open package in pool: @| %s", err)
|
|
}
|
|
return nil, err
|
|
}
|
|
defer func() { _ = reader.Close() }()
|
|
|
|
contents, err := GetContentsFromDeb(reader, file.Filename)
|
|
if err != nil {
|
|
if progress != nil {
|
|
progress.ColoredPrintf("@y[!]@| @!Failed to generate package contents: @| %s", err)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return contents, nil
|
|
}
|
|
|
|
// UpdateFiles saves new state of files
|
|
func (p *Package) UpdateFiles(files PackageFiles) {
|
|
p.files = &files
|
|
p.FilesHash = files.Hash()
|
|
}
|
|
|
|
// Stanza creates original stanza from package
|
|
func (p *Package) Stanza() (result Stanza) {
|
|
result = p.Extra().Copy()
|
|
result["Package"] = p.Name
|
|
result["Version"] = p.Version
|
|
|
|
if p.IsSource {
|
|
result["Architecture"] = p.SourceArchitecture
|
|
} else {
|
|
result["Architecture"] = p.Architecture
|
|
if p.Source != "" {
|
|
result["Source"] = p.Source
|
|
}
|
|
}
|
|
|
|
if p.IsSource {
|
|
md5, sha1, sha256, sha512 := []string{}, []string{}, []string{}, []string{}
|
|
|
|
for _, f := range p.Files() {
|
|
if f.Checksums.MD5 != "" {
|
|
md5 = append(md5, fmt.Sprintf(" %s %d %s\n", f.Checksums.MD5, f.Checksums.Size, f.Filename))
|
|
}
|
|
if f.Checksums.SHA1 != "" {
|
|
sha1 = append(sha1, fmt.Sprintf(" %s %d %s\n", f.Checksums.SHA1, f.Checksums.Size, f.Filename))
|
|
}
|
|
if f.Checksums.SHA256 != "" {
|
|
sha256 = append(sha256, fmt.Sprintf(" %s %d %s\n", f.Checksums.SHA256, f.Checksums.Size, f.Filename))
|
|
}
|
|
if f.Checksums.SHA512 != "" {
|
|
sha512 = append(sha512, fmt.Sprintf(" %s %d %s\n", f.Checksums.SHA512, f.Checksums.Size, f.Filename))
|
|
}
|
|
}
|
|
|
|
result["Files"] = strings.Join(md5, "")
|
|
if len(sha1) > 0 {
|
|
result["Checksums-Sha1"] = strings.Join(sha1, "")
|
|
}
|
|
if len(sha256) > 0 {
|
|
result["Checksums-Sha256"] = strings.Join(sha256, "")
|
|
}
|
|
if len(sha512) > 0 {
|
|
result["Checksums-Sha512"] = strings.Join(sha512, "")
|
|
}
|
|
} else if p.IsInstaller {
|
|
sha256 := []string{}
|
|
for _, f := range p.Files() {
|
|
sha256 = append(sha256, fmt.Sprintf("%s %s", f.Checksums.SHA256, f.Filename))
|
|
}
|
|
result[""] = strings.Join(sha256, "\n")
|
|
} else {
|
|
f := p.Files()[0]
|
|
result["Filename"] = f.DownloadURL()
|
|
if f.Checksums.MD5 != "" {
|
|
result["MD5sum"] = f.Checksums.MD5
|
|
}
|
|
if f.Checksums.SHA1 != "" {
|
|
result["SHA1"] = f.Checksums.SHA1
|
|
}
|
|
if f.Checksums.SHA256 != "" {
|
|
result["SHA256"] = f.Checksums.SHA256
|
|
}
|
|
if f.Checksums.SHA512 != "" {
|
|
result["SHA512"] = f.Checksums.SHA512
|
|
}
|
|
result["Size"] = fmt.Sprintf("%d", f.Checksums.Size)
|
|
}
|
|
|
|
deps := p.Deps()
|
|
|
|
if deps.Depends != nil {
|
|
result["Depends"] = strings.Join(deps.Depends, ", ")
|
|
}
|
|
if deps.PreDepends != nil {
|
|
result["Pre-Depends"] = strings.Join(deps.PreDepends, ", ")
|
|
}
|
|
if deps.Suggests != nil {
|
|
result["Suggests"] = strings.Join(deps.Suggests, ", ")
|
|
}
|
|
if deps.Recommends != nil {
|
|
result["Recommends"] = strings.Join(deps.Recommends, ", ")
|
|
}
|
|
if p.Provides != nil {
|
|
result["Provides"] = strings.Join(p.Provides, ", ")
|
|
}
|
|
if deps.BuildDepends != nil {
|
|
result["Build-Depends"] = strings.Join(deps.BuildDepends, ", ")
|
|
}
|
|
if deps.BuildDependsInDep != nil {
|
|
result["Build-Depends-Indep"] = strings.Join(deps.BuildDependsInDep, ", ")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Equals compares two packages to be identical
|
|
func (p *Package) Equals(p2 *Package) bool {
|
|
return p.Name == p2.Name && p.Version == p2.Version && p.SourceArchitecture == p2.SourceArchitecture &&
|
|
p.Architecture == p2.Architecture && p.Source == p2.Source && p.IsSource == p2.IsSource &&
|
|
p.FilesHash == p2.FilesHash
|
|
}
|
|
|
|
// LinkFromPool links package file from pool to dist's pool location
|
|
func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packagePool aptly.PackagePool,
|
|
prefix, relPath string, force bool) error {
|
|
|
|
for i, f := range p.Files() {
|
|
sourcePoolPath, err := f.GetPoolPath(packagePool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = publishedStorage.LinkFromPool(prefix, relPath, f.Filename, packagePool, sourcePoolPath, f.Checksums, force)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if p.IsSource {
|
|
p.Extra()["Directory"] = relPath
|
|
} else {
|
|
p.Files()[i].downloadPath = relPath
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PoolDirectory returns directory in package pool of published repository for this package files
|
|
func (p *Package) PoolDirectory() (string, error) {
|
|
source := p.Source
|
|
if source == "" {
|
|
source = p.Name
|
|
} else if pos := strings.Index(source, "("); pos != -1 {
|
|
source = strings.TrimSpace(source[:pos])
|
|
}
|
|
|
|
if len(source) < 2 {
|
|
return "", fmt.Errorf("package source %s too short", source)
|
|
}
|
|
|
|
var subdir string
|
|
if strings.HasPrefix(source, "lib") {
|
|
subdir = source[:4]
|
|
} else {
|
|
subdir = source[:1]
|
|
|
|
}
|
|
|
|
return filepath.Join(subdir, source), nil
|
|
}
|
|
|
|
// PackageDownloadTask is a element of download queue for the package
|
|
type PackageDownloadTask struct {
|
|
File *PackageFile
|
|
Additional []PackageDownloadTask
|
|
TempDownPath string
|
|
Done bool
|
|
}
|
|
|
|
// DownloadList returns list of missing package files for download in format
|
|
// [[srcpath, dstpath]]
|
|
func (p *Package) DownloadList(packagePool aptly.PackagePool, checksumStorage aptly.ChecksumStorage) (result []PackageDownloadTask, err error) {
|
|
result = make([]PackageDownloadTask, 0, 1)
|
|
|
|
files := p.Files()
|
|
for idx := range files {
|
|
verified, err := files[idx].Verify(packagePool, checksumStorage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !verified {
|
|
result = append(result, PackageDownloadTask{File: &files[idx]})
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// VerifyFiles verifies that all package files have neen correctly downloaded
|
|
func (p *Package) VerifyFiles(packagePool aptly.PackagePool, checksumStorage aptly.ChecksumStorage) (result bool, err error) {
|
|
result = true
|
|
|
|
for _, f := range p.Files() {
|
|
result, err = f.Verify(packagePool, checksumStorage)
|
|
if err != nil || !result {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// FilepathList returns list of paths to files in package repository
|
|
func (p *Package) FilepathList(packagePool aptly.PackagePool) ([]string, error) {
|
|
var err error
|
|
result := make([]string, len(p.Files()))
|
|
|
|
for i, f := range p.Files() {
|
|
result[i], err = f.GetPoolPath(packagePool)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|