services/internal/download/download.go
Dan Anglin 25325e2856
refactor: update project structure
Changes:

- Most of the go code is now located in internal packages.
- Code refactored and simplified in some cases.
- Removed the 'download' mage target and integrated the download code
  into the internal 'prepare' package.
- Moved all mage target code to magefile.go.
- Added missing descriptions to the mage targets.
- Updated go.mod.

Fixed:

- Created a custom function to validate the checksum of the
  downloaded Woodpecker tar file.
- Specified the environment when running 'clean'.
2023-11-24 09:56:35 +00:00

142 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
}