From 061e0eb6acd2ef28938dc5a71bbdd79e816ed753 Mon Sep 17 00:00:00 2001 From: Denis Pynkin Date: Mon, 25 Sep 2017 20:58:31 +0300 Subject: [PATCH] Rework of archiver Common approach for working with archives is introduced. Unpacker and Archiver interfaces are added. Example of file unpacking: archive, _ := debos.NewArchive("file.ext") archive.Unpack("destination") Three archive types are supported: - tar - zip - debian packages Type of archive is guessed by extension or can be set explicitly during archiver creation with option for NewArchive(). Signed-off-by: Denis Pynkin --- archiver.go | 214 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 199 insertions(+), 15 deletions(-) diff --git a/archiver.go b/archiver.go index 357e7f3..e67fdf6 100644 --- a/archiver.go +++ b/archiver.go @@ -1,33 +1,217 @@ package debos import ( - "log" + "fmt" "os" + "path/filepath" + "strings" ) -func TarOptions(compression string) string { +type ArchiveType int + +// Supported types +const ( + _ ArchiveType = iota // Guess archive type from file extension + Tar + Zip + Deb +) + +type ArchiveBase struct { + file string // Path to archive file + atype ArchiveType + options map[interface{}]interface{} // Archiver-depending map with additional hints +} +type ArchiveTar struct { + ArchiveBase +} +type ArchiveZip struct { + ArchiveBase +} +type ArchiveDeb struct { + ArchiveBase +} + +type Unpacker interface { + Unpack(destination string) error + RelaxedUnpack(destination string) error +} + +type Archiver interface { + Type() ArchiveType + AddOption(key, value interface{}) error + Unpacker +} + +type Archive struct { + Archiver +} + +// Unpack archive as is +func (arc *ArchiveBase) Unpack(destination string) error { + return fmt.Errorf("Unpack is not supported for '%s'", arc.file) +} + +/* +RelaxedUnpack unpack archive in relaxed mode allowing to ignore or +avoid minor issues with unpacker tool or framework. +*/ +func (arc *ArchiveBase) RelaxedUnpack(destination string) error { + return arc.Unpack(destination) +} + +func (arc *ArchiveBase) AddOption(key, value interface{}) error { + if arc.options == nil { + arc.options = make(map[interface{}]interface{}) + } + arc.options[key] = value + return nil +} + +func (arc *ArchiveBase) Type() ArchiveType { return arc.atype } + +// Helper function for unpacking with external tool +func unpack(command []string, destination string) error { + if err := os.MkdirAll(destination, 0755); err != nil { + return err + } + return Command{}.Run("unpack", command...) +} + +// Helper function for checking allowed compression types +// Returns empty string for unknown +func tarOptions(compression string) string { unpackTarOpts := map[string]string{ "gz": "-z", "bzip2": "-j", "xz": "-J", - } // Trying to guess all other supported formats + } // Trying to guess all other supported compression types return unpackTarOpts[compression] } -func UnpackTarArchive(infile, destination, compression string, options ...string) error { - if err := os.MkdirAll(destination, 0755); err != nil { - return err - } - log.Printf("Unpacking %s\n", infile) - +func (tar *ArchiveTar) Unpack(destination string) error { command := []string{"tar"} - command = append(command, options...) - command = append(command, "-x") - if unpackTarOpt := TarOptions(compression); len(unpackTarOpt) > 0 { - command = append(command, unpackTarOpt) + if options, ok := tar.options["taroptions"].([]string); ok { + for _, option := range options { + command = append(command, option) + } } - command = append(command, "-f", infile, "-C", destination) + command = append(command, "-C", destination) + command = append(command, "-x") - return Command{}.Run("unpack", command...) + if compression, ok := tar.options["tarcompression"]; ok { + if unpackTarOpt := tarOptions(compression.(string)); len(unpackTarOpt) > 0 { + command = append(command, unpackTarOpt) + } + } + command = append(command, "-f", tar.file) + + return unpack(command, destination) +} + +func (tar *ArchiveTar) RelaxedUnpack(destination string) error { + + taroptions := []string{"--no-same-owner", "--no-same-permissions"} + options, ok := tar.options["taroptions"].([]string) + defer func() { tar.options["taroptions"] = options }() + + if ok { + for _, option := range options { + taroptions = append(taroptions, option) + } + } + tar.options["taroptions"] = taroptions + + return tar.Unpack(destination) +} + +func (tar *ArchiveTar) AddOption(key, value interface{}) error { + + switch key { + case "taroptions": + // expect a slice + options, ok := value.([]string) + if !ok { + return fmt.Errorf("Wrong type for value") + } + tar.options["taroptions"] = options + + case "tarcompression": + compression, ok := value.(string) + if !ok { + return fmt.Errorf("Wrong type for value") + } + option := tarOptions(compression) + if len(option) == 0 { + return fmt.Errorf("Compression '%s' is not supported", compression) + } + tar.options["tarcompression"] = compression + + default: + return fmt.Errorf("Option '%v' is not supported for tar archive type", key) + } + return nil +} + +func (zip *ArchiveZip) Unpack(destination string) error { + command := []string{"unzip", zip.file, "-d", destination} + return unpack(command, destination) +} + +func (zip *ArchiveZip) RelaxedUnpack(destination string) error { + return zip.Unpack(destination) +} + +func (deb *ArchiveDeb) Unpack(destination string) error { + command := []string{"dpkg", "-x", deb.file, destination} + return unpack(command, destination) +} + +func (deb *ArchiveDeb) RelaxedUnpack(destination string) error { + return deb.Unpack(destination) +} + +/* +NewArchive associate correct structure and methods according to +archive type. If ArchiveType is omitted -- trying to guess the type. +Return ArchiveType or nil in case of error. +*/ +func NewArchive(file string, arcType ...ArchiveType) (Archive, error) { + var archive Archive + var atype ArchiveType + + if len(arcType) == 0 { + ext := filepath.Ext(file) + ext = strings.ToLower(ext) + + switch ext { + case ".deb": + atype = Deb + case ".zip": + atype = Zip + default: + //FIXME: guess Tar maybe? + atype = Tar + } + } else { + atype = arcType[0] + } + + common := ArchiveBase{} + common.file = file + common.atype = atype + common.options = make(map[interface{}]interface{}) + + switch atype { + case Tar: + archive = Archive{&ArchiveTar{ArchiveBase: common}} + case Zip: + archive = Archive{&ArchiveZip{ArchiveBase: common}} + case Deb: + archive = Archive{&ArchiveDeb{ArchiveBase: common}} + default: + return archive, fmt.Errorf("Unsupported archive '%s'", file) + } + return archive, nil }