143 lines
3 KiB
Go
143 lines
3 KiB
Go
|
package download
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
|
||
|
"github.com/magefile/mage/sh"
|
||
|
)
|
||
|
|
||
|
type Bundle struct {
|
||
|
DestinationDir string
|
||
|
Packages []Pack
|
||
|
Checksum Checksum
|
||
|
ValidateGPGSignature bool
|
||
|
}
|
||
|
|
||
|
type Pack struct {
|
||
|
File Object
|
||
|
GPGSignature Object
|
||
|
}
|
||
|
|
||
|
type Checksum struct {
|
||
|
File Object
|
||
|
Validate bool
|
||
|
ValidateFunc func() error
|
||
|
}
|
||
|
|
||
|
type Object struct {
|
||
|
Source string
|
||
|
Destination string
|
||
|
}
|
||
|
|
||
|
// Download downloads all the files in the download pack,
|
||
|
// verifies all the GPG signatures (if enabled) and
|
||
|
// verifies the checksums (if enabled).
|
||
|
func Download(bundle Bundle) error {
|
||
|
if err := os.MkdirAll(bundle.DestinationDir, 0o750); err != nil {
|
||
|
return fmt.Errorf("unable to make '%s'; %w", bundle.DestinationDir, err)
|
||
|
}
|
||
|
|
||
|
var objects []Object
|
||
|
|
||
|
for i := range bundle.Packages {
|
||
|
objects = append(objects, bundle.Packages[i].File)
|
||
|
if bundle.ValidateGPGSignature {
|
||
|
objects = append(objects, bundle.Packages[i].GPGSignature)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if bundle.Checksum.Validate {
|
||
|
objects = append(objects, bundle.Checksum.File)
|
||
|
}
|
||
|
|
||
|
for _, object := range objects {
|
||
|
if err := func() error {
|
||
|
_, err := os.Stat(object.Destination)
|
||
|
if err == nil {
|
||
|
fmt.Printf("%s is already downloaded.\n", object.Destination)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
file, err := os.Create(object.Destination)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("unable to create %s; %w", object.Destination, err)
|
||
|
}
|
||
|
defer file.Close()
|
||
|
|
||
|
client := http.Client{
|
||
|
CheckRedirect: func(r *http.Request, _ []*http.Request) error {
|
||
|
r.URL.Opaque = r.URL.Path
|
||
|
return nil
|
||
|
},
|
||
|
}
|
||
|
|
||
|
resp, err := client.Get(object.Source)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
size, err := io.Copy(file, resp.Body)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Downloaded %s with size %d.\n", object.Destination, size)
|
||
|
|
||
|
return nil
|
||
|
}(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if bundle.ValidateGPGSignature {
|
||
|
for i := range bundle.Packages {
|
||
|
if err := sh.RunV("gpg", "--verify", bundle.Packages[i].GPGSignature.Destination, bundle.Packages[i].File.Destination); err != nil {
|
||
|
return fmt.Errorf("GPG verification failed for '%s'; %w", bundle.Packages[i].File.Destination, err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if bundle.Checksum.Validate {
|
||
|
var err error
|
||
|
if bundle.Checksum.ValidateFunc != nil {
|
||
|
err = bundle.Checksum.ValidateFunc()
|
||
|
} else {
|
||
|
err = validateChecksum(bundle.DestinationDir, bundle.Checksum.File.Destination)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("checksum validation failed; %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func validateChecksum(destinationDir, checksumPath string) error {
|
||
|
startDir, err := os.Getwd()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := os.Chdir(destinationDir); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
checksum := filepath.Base(checksumPath)
|
||
|
|
||
|
if err := sh.RunV("sha256sum", "--check", "--ignore-missing", checksum); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := os.Chdir(startDir); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|