mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-06 05:30:57 +00:00
Support Acquire-By-Hash for index files
The added "aptly publish repo" option "-access-by-hash" publishes the index files (Packages*, Sources*) also as hardlinked hashes. Example: /dists/yakkety/main/binary-amd64/by-hash/SHA512/31833ec39acc... The Release files indicate this with the option "Acquire-By-Hash: yes" This is used by apt >= 1.2.0 and prevents the "Hash sum mismatch" race condition between a server side "aptly publish repo" and "apt-get update" on a client. See: http://www.chiark.greenend.org.uk/~cjwatson/blog/no-more-hash-sum-mismatch-errors.html This implementation uses symlinks in the by-hash/*/ directory for keeping only two versions of the index files and deleting older files automatically. Note: this only works with aptly.FileSystemPublishedStorage Closes: #536 Signed-off-by: André Roth <neolynx@gmail.com>
This commit is contained in:
committed by
Oliver Sauder
parent
a037615962
commit
bb2db7e500
@@ -238,6 +238,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
Component string `binding:"required"`
|
Component string `binding:"required"`
|
||||||
Name string `binding:"required"`
|
Name string `binding:"required"`
|
||||||
}
|
}
|
||||||
|
AccessByHash *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
if c.Bind(&b) != nil {
|
||||||
@@ -317,6 +318,10 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
published.SkipContents = *b.SkipContents
|
published.SkipContents = *b.SkipContents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.AccessByHash != nil {
|
||||||
|
published.AccessByHash = *b.AccessByHash
|
||||||
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite)
|
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(500, fmt.Errorf("unable to update: %s", err))
|
c.AbortWithError(500, fmt.Errorf("unable to update: %s", err))
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ Example:
|
|||||||
cmd.Flag.String("butautomaticupgrades", "", "set value for ButAutomaticUpgrades field")
|
cmd.Flag.String("butautomaticupgrades", "", "set value for ButAutomaticUpgrades field")
|
||||||
cmd.Flag.String("label", "", "label to publish")
|
cmd.Flag.String("label", "", "label to publish")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||||
|
cmd.Flag.Bool("access-by-hash", false, "provide index files by hash also")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
|
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.Flags().IsSet("access-by-hash") {
|
||||||
|
published.AccessByHash = context.Flags().Lookup("access-by-hash").Value.Get().(bool)
|
||||||
|
}
|
||||||
|
|
||||||
duplicate := context.CollectionFactory().PublishedRepoCollection().CheckDuplicate(published)
|
duplicate := context.CollectionFactory().PublishedRepoCollection().CheckDuplicate(published)
|
||||||
if duplicate != nil {
|
if duplicate != nil {
|
||||||
context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory())
|
context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory())
|
||||||
|
|||||||
+73
-2
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ type indexFiles struct {
|
|||||||
tempDir string
|
tempDir string
|
||||||
suffix string
|
suffix string
|
||||||
indexes map[string]*indexFile
|
indexes map[string]*indexFile
|
||||||
|
accessByHash bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type indexFile struct {
|
type indexFile struct {
|
||||||
@@ -28,6 +30,7 @@ type indexFile struct {
|
|||||||
compressable bool
|
compressable bool
|
||||||
onlyGzip bool
|
onlyGzip bool
|
||||||
signable bool
|
signable bool
|
||||||
|
accessByHash bool
|
||||||
relativePath string
|
relativePath string
|
||||||
tempFilename string
|
tempFilename string
|
||||||
tempFile *os.File
|
tempFile *os.File
|
||||||
@@ -91,11 +94,24 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
|||||||
file.parent.generatedFiles[file.relativePath+ext] = checksumInfo
|
file.parent.generatedFiles[file.relativePath+ext] = checksumInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.parent.publishedStorage.MkDir(filepath.Dir(filepath.Join(file.parent.basePath, file.relativePath)))
|
filedir := filepath.Dir(filepath.Join(file.parent.basePath, file.relativePath))
|
||||||
|
|
||||||
|
err = file.parent.publishedStorage.MkDir(filedir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create dir: %s", err)
|
return fmt.Errorf("unable to create dir: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashs := []string{}
|
||||||
|
if file.accessByHash {
|
||||||
|
hashs = append(hashs, "MD5", "SHA1", "SHA256", "SHA512")
|
||||||
|
for _, hash := range hashs {
|
||||||
|
err = file.parent.publishedStorage.MkDir(filepath.Join(filedir, "by-hash", hash))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create dir: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, ext := range exts {
|
for _, ext := range exts {
|
||||||
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext),
|
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext),
|
||||||
file.tempFilename+ext)
|
file.tempFilename+ext)
|
||||||
@@ -107,6 +123,29 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
|||||||
file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext)] =
|
file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext)] =
|
||||||
filepath.Join(file.parent.basePath, file.relativePath+ext)
|
filepath.Join(file.parent.basePath, file.relativePath+ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if file.accessByHash {
|
||||||
|
sums := file.parent.generatedFiles[file.relativePath+ext]
|
||||||
|
storage := file.parent.publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||||
|
src := filepath.Join(storage, file.parent.basePath, file.relativePath)
|
||||||
|
|
||||||
|
err = packageIndexByHash(src, file.parent.suffix, ext, storage, filedir, "SHA512", sums.SHA512)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
}
|
||||||
|
err = packageIndexByHash(src, file.parent.suffix, ext, storage, filedir, "SHA256", sums.SHA256)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
}
|
||||||
|
err = packageIndexByHash(src, file.parent.suffix, ext, storage, filedir, "SHA1", sums.SHA1)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
}
|
||||||
|
err = packageIndexByHash(src, file.parent.suffix, ext, storage, filedir, "MD5", sums.MD5)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.signable && signer != nil {
|
if file.signable && signer != nil {
|
||||||
@@ -143,7 +182,35 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, suffix string) *indexFiles {
|
func packageIndexByHash(src string, suffix string, ext string, storage string, filedir string, hash string, sum string) error {
|
||||||
|
indexfile := path.Base(src + ext)
|
||||||
|
src = src + suffix + ext
|
||||||
|
dst := filepath.Join(storage, filedir, "by-hash", hash)
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(dst, sum)); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := os.Link(src, filepath.Join(dst, sum))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Access-By-Hash: error creating hardlink %s", filepath.Join(dst, sum))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(dst, indexfile)); err == nil {
|
||||||
|
if _, err := os.Stat(filepath.Join(dst, indexfile+".old")); err == nil {
|
||||||
|
link, _ := os.Readlink(filepath.Join(dst, indexfile+".old"))
|
||||||
|
os.Remove(filepath.Join(dst, link))
|
||||||
|
os.Remove(filepath.Join(dst, indexfile+".old"))
|
||||||
|
}
|
||||||
|
os.Rename(filepath.Join(dst, indexfile), filepath.Join(dst, indexfile+".old"))
|
||||||
|
}
|
||||||
|
err = os.Symlink(sum, filepath.Join(dst, indexfile))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Access-By-Hash: error creating symlink %s", filepath.Join(dst, indexfile))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, suffix string, accessByHash bool) *indexFiles {
|
||||||
return &indexFiles{
|
return &indexFiles{
|
||||||
publishedStorage: publishedStorage,
|
publishedStorage: publishedStorage,
|
||||||
basePath: basePath,
|
basePath: basePath,
|
||||||
@@ -152,6 +219,7 @@ func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, s
|
|||||||
tempDir: tempDir,
|
tempDir: tempDir,
|
||||||
suffix: suffix,
|
suffix: suffix,
|
||||||
indexes: make(map[string]*indexFile),
|
indexes: make(map[string]*indexFile),
|
||||||
|
accessByHash: accessByHash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +247,7 @@ func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexF
|
|||||||
discardable: false,
|
discardable: false,
|
||||||
compressable: true,
|
compressable: true,
|
||||||
signable: false,
|
signable: false,
|
||||||
|
accessByHash: files.accessByHash,
|
||||||
relativePath: relativePath,
|
relativePath: relativePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +281,7 @@ func (files *indexFiles) ReleaseIndex(component, arch string, udeb bool) *indexF
|
|||||||
discardable: udeb,
|
discardable: udeb,
|
||||||
compressable: false,
|
compressable: false,
|
||||||
signable: false,
|
signable: false,
|
||||||
|
accessByHash: files.accessByHash,
|
||||||
relativePath: relativePath,
|
relativePath: relativePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +312,7 @@ func (files *indexFiles) ContentsIndex(component, arch string, udeb bool) *index
|
|||||||
compressable: true,
|
compressable: true,
|
||||||
onlyGzip: true,
|
onlyGzip: true,
|
||||||
signable: false,
|
signable: false,
|
||||||
|
accessByHash: files.accessByHash,
|
||||||
relativePath: relativePath,
|
relativePath: relativePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -64,6 +64,9 @@ type PublishedRepo struct {
|
|||||||
|
|
||||||
// True if repo is being re-published
|
// True if repo is being re-published
|
||||||
rePublishing bool
|
rePublishing bool
|
||||||
|
|
||||||
|
// Provide index files per hash also
|
||||||
|
AccessByHash bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePrefix splits [storage:]prefix into components
|
// ParsePrefix splits [storage:]prefix into components
|
||||||
@@ -556,7 +559,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(tempDir)
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix)
|
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix, p.AccessByHash)
|
||||||
|
|
||||||
for component, list := range lists {
|
for component, list := range lists {
|
||||||
hadUdebs := false
|
hadUdebs := false
|
||||||
@@ -683,6 +686,9 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
release["Component"] = component
|
release["Component"] = component
|
||||||
release["Origin"] = p.GetOrigin()
|
release["Origin"] = p.GetOrigin()
|
||||||
release["Label"] = p.GetLabel()
|
release["Label"] = p.GetLabel()
|
||||||
|
if p.AccessByHash {
|
||||||
|
release["Acquire-By-Hash"] = "yes"
|
||||||
|
}
|
||||||
|
|
||||||
var bufWriter *bufio.Writer
|
var bufWriter *bufio.Writer
|
||||||
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
||||||
@@ -720,6 +726,9 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
release["Codename"] = p.Distribution
|
release["Codename"] = p.Distribution
|
||||||
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
||||||
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
||||||
|
if p.AccessByHash {
|
||||||
|
release["Acquire-By-Hash"] = "yes"
|
||||||
|
}
|
||||||
release["Description"] = " Generated by aptly\n"
|
release["Description"] = " Generated by aptly\n"
|
||||||
release["MD5Sum"] = ""
|
release["MD5Sum"] = ""
|
||||||
release["SHA1"] = ""
|
release["SHA1"] = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user