1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
package debos
import (
"fmt"
"os"
"path/filepath"
"strings"
)
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 compression types
return unpackTarOpts[compression]
}
func (tar *ArchiveTar) Unpack(destination string) error {
command := []string{"tar"}
if options, ok := tar.options["taroptions"].([]string); ok {
for _, option := range options {
command = append(command, option)
}
}
command = append(command, "-C", destination)
command = append(command, "-x")
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
}
|