Files
debos/archiver.go
copilot-swe-agent[bot] f566e04888 chore: enable more linters and fix all critical issues
Enabled additional linters from fakemachine configuration:
- errorlint: Error wrapping with %w
- misspell: Spelling checks
- revive: Code quality checks
- whitespace: Formatting checks

Fixed all issues including:
- Error handling: Added proper error checks for all function returns
- Error wrapping: Changed %v to %w for proper error wrapping
- Type assertions: Used errors.As instead of direct type assertions
- Unused parameters: Renamed to underscore where appropriate
- Variable naming: Fixed ALL_CAPS constants and underscored names
- Whitespace: Removed unnecessary leading/trailing newlines
- Code flow: Removed unnecessary else blocks

Renamed types (breaking internal API changes):
- DebosState → State
- DebosContext → Context
- DownloadHttpUrl → DownloadHTTPURL

Fixed struct field naming with proper YAML tags:
- Url → URL (with yaml:"url" tag)
- TlsClientCertPath → TLSClientCertPath (kept yaml:"tls-client-cert-path")
- TlsClientKeyPath → TLSClientKeyPath (kept yaml:"tls-client-key-path")
- validateUrl → validateURL method

Co-authored-by: sjoerdsimons <22603932+sjoerdsimons@users.noreply.github.com>
2025-10-01 21:21:22 +02:00

229 lines
5.3 KiB
Go

package debos
import (
"fmt"
"os"
"os/exec"
"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(_ 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{
"bzip2": "--bzip2",
"gz": "--gzip",
"lzip": "--lzip",
"lzma": "--lzma",
"lzop": "--lzop",
"xz": "--xz",
"zstd": "--zstd",
} // Trying to guess all other supported compression types
return unpackTarOpts[compression]
}
func (tar *ArchiveTar) Unpack(destination string) error {
command := []string{"tar"}
usePigz := false
if compression, ok := tar.options["tarcompression"]; ok && compression == "gz" {
if _, err := exec.LookPath("pigz"); err == nil {
usePigz = true
}
}
if options, ok := tar.options["taroptions"].([]string); ok {
command = append(command, options...)
}
command = append(command, "-C", destination)
command = append(command, "-x")
command = append(command, "--xattrs")
command = append(command, "--xattrs-include=*.*")
if compression, ok := tar.options["tarcompression"]; ok {
if unpackTarOpt := tarOptions(compression.(string)); len(unpackTarOpt) > 0 {
if usePigz {
command = append(command, "--use-compress-program=pigz")
} else {
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 {
taroptions = append(taroptions, options...)
}
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
}