This commit introduces the ability to download release assets from GitHub. It adds two new subcommands: `borg collect github releases` to download all releases for a repository, and `borg collect github release` to download a specific release. Both commands support the following options: * `--assets-only`: Skip release notes and only download the assets. * `--pattern`: Filter assets by a filename pattern. * `--verify-checksums`: Verify the checksums of the downloaded assets. To handle large binary files efficiently, the download logic has been refactored to stream the assets directly to disk, avoiding loading the entire file into memory. The commit also includes: * Unit tests for the new subcommands and their options. * Updated tests for the `pkg/github` package to reflect the new streaming download implementation. * A fix for the `collect_github_release` example to work with the new streaming download implementation. I have been unable to get all the tests to pass due to issues with mocking and the test environment setup. I believe I am very close to a solution, but I have exhausted my attempts. Co-authored-by: Snider <631881+Snider@users.noreply.github.com>
131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
borg_github "github.com/Snider/Borg/pkg/github"
|
|
"bytes"
|
|
"github.com/google/go-github/v39/github"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func NewCollectGithubReleaseCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "release <owner/repo> [tag]",
|
|
Short: "Download a release from GitHub",
|
|
Long: `Download a specific release from GitHub. If no tag is specified, the latest release will be downloaded.`,
|
|
Args: cobra.RangeArgs(1, 2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
logVal := cmd.Context().Value("logger")
|
|
log, ok := logVal.(*slog.Logger)
|
|
if !ok || log == nil {
|
|
return errors.New("logger not properly initialised")
|
|
}
|
|
repoURL := args[0]
|
|
outputDir, _ := cmd.Flags().GetString("output")
|
|
assetsOnly, _ := cmd.Flags().GetBool("assets-only")
|
|
pattern, _ := cmd.Flags().GetString("pattern")
|
|
verifyChecksums, _ := cmd.Flags().GetBool("verify-checksums")
|
|
|
|
owner, repo, err := borg_github.ParseRepoFromURL(repoURL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse repository url: %w", err)
|
|
}
|
|
|
|
var release *github.RepositoryRelease
|
|
if len(args) == 2 {
|
|
tag := args[1]
|
|
log.Info("getting release by tag", "tag", tag)
|
|
release, err = borg_github.GetReleaseByTag(owner, repo, tag)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get release '%s': %w", tag, err)
|
|
}
|
|
} else {
|
|
log.Info("getting latest release")
|
|
release, err = borg_github.GetLatestRelease(owner, repo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get latest release: %w", err)
|
|
}
|
|
}
|
|
if release == nil {
|
|
return errors.New("release not found")
|
|
}
|
|
|
|
log.Info("found release", "tag", release.GetTagName())
|
|
|
|
tag := release.GetTagName()
|
|
releaseDir := filepath.Join(outputDir, tag)
|
|
if err := os.MkdirAll(releaseDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create release directory: %w", err)
|
|
}
|
|
|
|
if !assetsOnly {
|
|
releaseNotes := release.GetBody()
|
|
if err := os.WriteFile(filepath.Join(releaseDir, "RELEASE.md"), []byte(releaseNotes), 0644); err != nil {
|
|
return fmt.Errorf("failed to write release notes: %w", err)
|
|
}
|
|
}
|
|
|
|
for _, asset := range release.Assets {
|
|
if pattern != "" {
|
|
matched, err := filepath.Match(pattern, asset.GetName())
|
|
if err != nil {
|
|
return fmt.Errorf("invalid pattern: %w", err)
|
|
}
|
|
if !matched {
|
|
continue
|
|
}
|
|
}
|
|
|
|
log.Info("downloading asset", "name", asset.GetName())
|
|
assetPath := filepath.Join(releaseDir, asset.GetName())
|
|
file, err := os.Create(assetPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create asset file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var checksumData []byte
|
|
if verifyChecksums {
|
|
checksumAsset := findChecksumAsset(release.Assets)
|
|
if checksumAsset == nil {
|
|
log.Warn("checksum file not found in release", "tag", tag)
|
|
} else {
|
|
buf := new(bytes.Buffer)
|
|
err := borg_github.DownloadReleaseAsset(checksumAsset, buf)
|
|
if err != nil {
|
|
log.Error("failed to download checksum file", "name", checksumAsset.GetName(), "err", err)
|
|
} else {
|
|
checksumData = buf.Bytes()
|
|
}
|
|
}
|
|
}
|
|
|
|
if verifyChecksums && checksumData != nil {
|
|
err = borg_github.DownloadReleaseAssetWithChecksum(asset, checksumData, file)
|
|
} else {
|
|
err = borg_github.DownloadReleaseAsset(asset, file)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Error("failed to download asset", "name", asset.GetName(), "err", err)
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().String("output", "releases", "Output directory for the releases")
|
|
cmd.Flags().Bool("assets-only", false, "Only download assets, skip release notes")
|
|
cmd.Flags().String("pattern", "", "Filter assets by filename pattern")
|
|
cmd.Flags().Bool("verify-checksums", false, "Verify checksums after download")
|
|
return cmd
|
|
}
|
|
|
|
func init() {
|
|
collectGithubCmd.AddCommand(NewCollectGithubReleaseCmd())
|
|
}
|