package cmd import ( "errors" "fmt" "log/slog" "os" "path/filepath" "github.com/Snider/Borg/pkg/datanode" borg_github "github.com/Snider/Borg/pkg/github" "github.com/google/go-github/v39/github" "github.com/spf13/cobra" "golang.org/x/mod/semver" ) func NewCollectGithubReleaseCmd() *cobra.Command { cmd := &cobra.Command{ Use: "release [repository-url]", Short: "Download the latest release of a file from GitHub releases", Long: `Download the latest release of a file from GitHub releases. If the file or URL has a version number, it will check for a higher version and download it if found.`, Args: cobra.ExactArgs(1), 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") pack, _ := cmd.Flags().GetBool("pack") file, _ := cmd.Flags().GetString("file") version, _ := cmd.Flags().GetString("version") _, err := GetRelease(log, repoURL, outputDir, pack, file, version) return err }, } cmd.PersistentFlags().String("output", ".", "Output directory for the downloaded file") cmd.PersistentFlags().Bool("pack", false, "Pack all assets into a DataNode") cmd.PersistentFlags().String("file", "", "The file to download from the release") cmd.PersistentFlags().String("version", "", "The version to check against") return cmd } func init() { collectGithubCmd.AddCommand(NewCollectGithubReleaseCmd()) } func GetRelease(log *slog.Logger, repoURL string, outputDir string, pack bool, file string, version string) (*github.RepositoryRelease, error) { owner, repo, err := borg_github.ParseRepoFromURL(repoURL) if err != nil { return nil, fmt.Errorf("failed to parse repository url: %w", err) } release, err := borg_github.GetLatestRelease(owner, repo) if err != nil { return nil, fmt.Errorf("failed to get latest release: %w", err) } log.Info("found latest release", "tag", release.GetTagName()) if version != "" { tag := release.GetTagName() if !semver.IsValid(tag) { log.Info("latest release tag is not a valid semantic version, skipping comparison", "tag", tag) } else { if !semver.IsValid(version) { return nil, fmt.Errorf("invalid version string: %s", version) } if semver.Compare(tag, version) <= 0 { log.Info("latest release is not newer than the provided version", "latest", tag, "provided", version) return nil, nil } } } if pack { dn := datanode.New() var failedAssets []string for _, asset := range release.Assets { log.Info("downloading asset", "name", asset.GetName()) data, err := borg_github.DownloadReleaseAsset(asset) if err != nil { log.Error("failed to download asset", "name", asset.GetName(), "err", err) failedAssets = append(failedAssets, asset.GetName()) continue } dn.AddData(asset.GetName(), data) } if len(failedAssets) > 0 { return nil, fmt.Errorf("failed to download assets: %v", failedAssets) } tar, err := dn.ToTar() if err != nil { return nil, fmt.Errorf("failed to create datanode: %w", err) } if err := os.MkdirAll(outputDir, 0755); err != nil { return nil, fmt.Errorf("failed to create output directory: %w", err) } basename := release.GetTagName() if basename == "" { basename = "release" } outputFile := filepath.Join(outputDir, basename+".dat") err = os.WriteFile(outputFile, tar, 0644) if err != nil { return nil, fmt.Errorf("failed to write datanode: %w", err) } log.Info("datanode saved", "path", outputFile) } else { if len(release.Assets) == 0 { log.Info("no assets found in the latest release") return nil, nil } var assetToDownload *github.ReleaseAsset if file != "" { for _, asset := range release.Assets { if asset.GetName() == file { assetToDownload = asset break } } if assetToDownload == nil { return nil, fmt.Errorf("asset not found in the latest release: %s", file) } } else { assetToDownload = release.Assets[0] } if outputDir != "" { if err := os.MkdirAll(outputDir, 0755); err != nil { return nil, fmt.Errorf("failed to create output directory: %w", err) } } outputPath := filepath.Join(outputDir, assetToDownload.GetName()) log.Info("downloading asset", "name", assetToDownload.GetName()) data, err := borg_github.DownloadReleaseAsset(assetToDownload) if err != nil { return nil, fmt.Errorf("failed to download asset: %w", err) } err = os.WriteFile(outputPath, data, 0644) if err != nil { return nil, fmt.Errorf("failed to write asset to file: %w", err) } log.Info("asset downloaded", "path", outputPath) } return release, nil }