diff --git a/debian/publish.go b/debian/publish.go new file mode 100644 index 00000000..41cc6c1a --- /dev/null +++ b/debian/publish.go @@ -0,0 +1,168 @@ +package debian + +import ( + "bufio" + "fmt" + "github.com/smira/aptly/utils" + "path/filepath" + "strings" + "time" +) + +// PublishedRepo is a published for http/ftp representation of snapshot as Debian repository +type PublishedRepo struct { + // Prefix & distribution should be unique across all published repositories + Prefix string + Distribution string + Component string + // Architectures is a list of all architectures published + Architectures []string + // Snapshot as a source of publishing + SnapshotUUID string + + snapshot *Snapshot +} + +// NewPublishedRepo creates new published repository +func NewPublishedRepo(prefix string, distribution string, component string, architectures []string, snapshot *Snapshot) *PublishedRepo { + return &PublishedRepo{ + Prefix: prefix, + Distribution: distribution, + Component: component, + Architectures: architectures, + SnapshotUUID: snapshot.UUID, + snapshot: snapshot, + } +} + +// Publish publishes snapshot (repository) contents, links package files, generates Packages & Release files, signs them +func (p *PublishedRepo) Publish(repo *Repository, packageCollection *PackageCollection, signer utils.Signer) error { + err := repo.MkDir(filepath.Join(p.Prefix, "pool")) + if err != nil { + return err + } + basePath := filepath.Join(p.Prefix, "dists", p.Distribution) + err = repo.MkDir(basePath) + if err != nil { + return err + } + + // Load all packages + list := NewPackageList() + + p.snapshot.RefList().ForEach(func(key []byte) { + pkg, _ := packageCollection.ByKey(key) + list.Add(pkg) + }) + + // TODO: verify/guess list of architectures + + generatedFiles := map[string]*utils.ChecksumInfo{} + + // For all architectures, generate release file + for _, arch := range p.Architectures { + relativePath := filepath.Join(p.Component, fmt.Sprintf("binary-%s", arch), "Packages") + err = repo.MkDir(filepath.Dir(filepath.Join(basePath, relativePath))) + if err != nil { + return err + } + + packagesFile, err := repo.CreateFile(filepath.Join(basePath, relativePath)) + if err != nil { + return fmt.Errorf("unable to creates Packages file: %s", err) + } + + bufWriter := bufio.NewWriter(packagesFile) + + list.ForEach(func(pkg *Package) { + if pkg.Architecture == arch || pkg.Architecture == "all" { + // TODO: error handling + pkg.Stanza().WriteTo(bufWriter) + bufWriter.WriteByte('\n') + + repo.LinkFromPool(p.Prefix, p.Component, pkg.Filename, pkg.HashMD5) + } + }) + + err = bufWriter.Flush() + if err != nil { + return fmt.Errorf("unable to write Packages file: %s", err) + } + + err = utils.CompressFile(packagesFile) + if err != nil { + return fmt.Errorf("unable to compress Packages files: %s", err) + } + + packagesFile.Close() + + checksumInfo, err := repo.ChecksumsForFile(filepath.Join(basePath, relativePath)) + if err != nil { + return fmt.Errorf("unable to collect checksums: %s", err) + } + generatedFiles[relativePath] = checksumInfo + + checksumInfo, err = repo.ChecksumsForFile(filepath.Join(basePath, relativePath+".gz")) + if err != nil { + return fmt.Errorf("unable to collect checksums: %s", err) + } + generatedFiles[relativePath+".gz"] = checksumInfo + + checksumInfo, err = repo.ChecksumsForFile(filepath.Join(basePath, relativePath+".bz2")) + if err != nil { + return fmt.Errorf("unable to collect checksums: %s", err) + } + generatedFiles[relativePath+".bz2"] = checksumInfo + + } + + release := make(Stanza) + release["Origin"] = p.Prefix + " " + p.Distribution + release["Label"] = p.Prefix + " " + p.Distribution + release["Codename"] = p.Distribution + release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST") + release["Components"] = p.Component + release["Architectures"] = strings.Join(p.Architectures, " ") + release["Description"] = "Generated by aptly\n" + release["MD5Sum"] = "\n" + release["SHA1"] = "\n" + release["SHA256"] = "\n" + + for path, info := range generatedFiles { + 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) + } + + releaseFile, err := repo.CreateFile(filepath.Join(basePath, "Release")) + if err != nil { + return fmt.Errorf("unable to create Release file: %s", err) + } + + bufWriter := bufio.NewWriter(releaseFile) + + err = release.WriteTo(bufWriter) + if err != nil { + return fmt.Errorf("unable to create Release file: %s", err) + } + + err = bufWriter.Flush() + if err != nil { + return fmt.Errorf("unable to create Release file: %s", err) + } + + releaseFilename := releaseFile.Name() + releaseFile.Close() + + err = signer.DetachedSign(releaseFilename, releaseFilename+".gpg") + if err != nil { + return fmt.Errorf("unable to sign Release file: %s", err) + } + + err = signer.ClearSign(releaseFilename, filepath.Join(filepath.Dir(releaseFilename), "InRelease")) + if err != nil { + return fmt.Errorf("unable to sign Release file: %s", err) + } + + return nil +}