diff --git a/aptly/interfaces.go b/aptly/interfaces.go index 9e9f88dc..c79b6e15 100644 --- a/aptly/interfaces.go +++ b/aptly/interfaces.go @@ -34,7 +34,7 @@ type PublishedStorage interface { // Remove removes single file under public path Remove(path string) error // LinkFromPool links package file from pool to dist's pool location - LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath, sourceMD5 string) error + LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath, sourceMD5 string, force bool) error // Filelist returns list of files under prefix Filelist(prefix string) ([]string, error) // RenameFile renames (moves) file diff --git a/deb/package.go b/deb/package.go index 9cd78c00..b6be2453 100644 --- a/deb/package.go +++ b/deb/package.go @@ -477,7 +477,7 @@ func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packageP relPath := filepath.Join("pool", component, poolDir) publishedDirectory := filepath.Join(prefix, relPath) - err = publishedStorage.LinkFromPool(publishedDirectory, packagePool, sourcePath, f.Checksums.MD5) + err = publishedStorage.LinkFromPool(publishedDirectory, packagePool, sourcePath, f.Checksums.MD5, false) if err != nil { return err } diff --git a/files/public.go b/files/public.go index 33f7f430..5ecb1c36 100644 --- a/files/public.go +++ b/files/public.go @@ -79,7 +79,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress // sourcePath is filepath to package file in package pool // // LinkFromPool returns relative path for the published file to be included in package index -func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, sourcePath, sourceMD5 string) error { +func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, + sourcePath, sourceMD5 string, force bool) error { // verify that package pool is local pool is filesystem pool _ = sourcePool.(*PackagePool) @@ -105,10 +106,18 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourceP srcSys := srcStat.Sys().(*syscall.Stat_t) dstSys := dstStat.Sys().(*syscall.Stat_t) - if srcSys.Ino != dstSys.Ino { - return fmt.Errorf("error linking file to %s: file already exists and is different", filepath.Join(poolPath, baseName)) + if srcSys.Ino == dstSys.Ino { + return nil + } + + if !force { + return fmt.Errorf("error linking file to %s: file already exists and is different", filepath.Join(poolPath, baseName)) + } else { + err = os.Remove(filepath.Join(poolPath, baseName)) + if err != nil { + return err + } } - return nil } return os.Link(sourcePath, filepath.Join(poolPath, baseName)) diff --git a/files/public_test.go b/files/public_test.go index cbb1bc5a..b619465f 100644 --- a/files/public_test.go +++ b/files/public_test.go @@ -153,7 +153,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { err = ioutil.WriteFile(t.sourcePath, []byte("Contents"), 0644) c.Assert(err, IsNil) - err = s.storage.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, "") + err = s.storage.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, "", false) c.Assert(err, IsNil) st, err := os.Stat(filepath.Join(s.storage.rootPath, t.prefix, t.expectedFilename)) @@ -171,6 +171,22 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { err = ioutil.WriteFile(sourcePath, []byte("Contents"), 0644) c.Assert(err, IsNil) - err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "") + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "", false) c.Check(err, ErrorMatches, ".*file already exists and is different") + + st, err := os.Stat(sourcePath) + c.Assert(err, IsNil) + + info := st.Sys().(*syscall.Stat_t) + c.Check(int(info.Nlink), Equals, 1) + + // linking with force + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "", true) + c.Check(err, IsNil) + + st, err = os.Stat(sourcePath) + c.Assert(err, IsNil) + + info = st.Sys().(*syscall.Stat_t) + c.Check(int(info.Nlink), Equals, 2) } diff --git a/s3/public.go b/s3/public.go index 8f5e059f..08a8cd6e 100644 --- a/s3/public.go +++ b/s3/public.go @@ -140,7 +140,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress // sourcePath is filepath to package file in package pool // // LinkFromPool returns relative path for the published file to be included in package index -func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, sourcePath, sourceMD5 string) error { +func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, + sourcePath, sourceMD5 string, force bool) error { // verify that package pool is local pool in filesystem _ = sourcePool.(*files.PackagePool) @@ -159,9 +160,15 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourceP return fmt.Errorf("error getting information about %s from %s: %s", poolPath, storage, err) } } else { - if strings.Replace(dstKey.ETag, "\"", "", -1) == sourceMD5 { + destinationMD5 := strings.Replace(dstKey.ETag, "\"", "", -1) + if destinationMD5 == sourceMD5 { return nil } + + if !force && destinationMD5 != sourceMD5 { + return fmt.Errorf("error putting file to %s: file already exists and is different: %s", poolPath, storage) + + } } return storage.PutFile(relPath, sourcePath) diff --git a/s3/public_test.go b/s3/public_test.go index 6b24891e..8d54be3a 100644 --- a/s3/public_test.go +++ b/s3/public_test.go @@ -3,8 +3,10 @@ package s3 import ( "github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/s3/s3test" + "github.com/smira/aptly/files" "io/ioutil" . "launchpad.net/gocheck" + "os" "path/filepath" ) @@ -114,3 +116,58 @@ func (s *PublishedStorageSuite) TestRemoveDirs(c *C) { c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a", "lala/b", "lala/c", "test/a", "test/b", "testa"}) } + +func (s *PublishedStorageSuite) TestRenameFile(c *C) { + c.Skip("copy not available in s3test") +} + +func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { + root := c.MkDir() + pool := files.NewPackagePool(root) + + sourcePath := filepath.Join(root, "pool/c1/df/mars-invaders_1.03.deb") + err := os.MkdirAll(filepath.Dir(sourcePath), 0755) + c.Assert(err, IsNil) + + err = ioutil.WriteFile(sourcePath, []byte("Contents"), 0644) + c.Assert(err, IsNil) + + sourcePath2 := filepath.Join(root, "pool/e9/df/mars-invaders_1.03.deb") + err = os.MkdirAll(filepath.Dir(sourcePath2), 0755) + c.Assert(err, IsNil) + + err = ioutil.WriteFile(sourcePath2, []byte("Spam"), 0644) + c.Assert(err, IsNil) + + // first link from pool + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "c1df1da7a1ce305a3b60af9d5733ac1d", false) + c.Check(err, IsNil) + + data, err := s.storage.bucket.Get("pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Contents")) + + // duplicate link from pool + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "c1df1da7a1ce305a3b60af9d5733ac1d", false) + c.Check(err, IsNil) + + data, err = s.storage.bucket.Get("pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Contents")) + + // link from pool with conflict + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath2, "e9dfd31cc505d51fc26975250750deab", false) + c.Check(err, ErrorMatches, ".*file already exists and is different.*") + + data, err = s.storage.bucket.Get("pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Contents")) + + // link from pool with conflict and force + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath2, "e9dfd31cc505d51fc26975250750deab", true) + c.Check(err, IsNil) + + data, err = s.storage.bucket.Get("pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Spam")) +}