pkg/make-release/utils.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
osPkg "os"
"os/exec"
"path/filepath"
"strings"
"github.com/cockroachdb/errors"
)
type Release struct {
Version string // The version of the release
Filename string // The filename of the release (inc extension)
FileExt string // The file extension
URL string // The URL to download the release from
Checksum []byte // The checksum of the release
}
// getGithubRelease fetches the latest release from Github for the given org and repo
func getGithubRelease(org string, repo string, os string, arch string) (*Release, error) {
rtn := &Release{}
type GithubRelease struct {
TagName string `json:"tag_name"`
Assets []struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
}
// Download the latest releases
releasesResp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", org, repo))
if err != nil {
return nil, errors.Wrap(err, "unable to fetch latest release information")
}
defer func() { _ = releasesResp.Body.Close() }()
if releasesResp.StatusCode != http.StatusOK {
return nil, errors.Newf("Unexpected response status code: %s", releasesResp.Status)
}
releases := &GithubRelease{}
if err := json.NewDecoder(releasesResp.Body).Decode(releases); err != nil {
return nil, errors.Wrap(err, "unable to decode Github releases")
}
rtn.Version = releases.TagName
// Build a list of possible file names
osOptions := []string{os}
if os == "darwin" {
osOptions = append(osOptions, "macos")
}
archOptions := []string{arch}
if arch == "amd64" {
archOptions = append(archOptions, "x86_64", "x86-64")
} else if arch == "arm64" {
archOptions = append(archOptions, "aarch64")
}
extOptions := []string{"tar.gz"} // , "zip"}
var fileNameOptions []string
for _, osOption := range osOptions {
for _, archOption := range archOptions {
for _, extOption := range extOptions {
fileNameOptions = append(fileNameOptions,
fmt.Sprintf("%s_%s.%s", osOption, archOption, extOption),
fmt.Sprintf("%s-%s.%s", osOption, archOption, extOption),
)
}
}
}
// Find the checksum file
checksumFileURL := ""
for _, asset := range releases.Assets {
// We want to know the checksum URL
if strings.EqualFold(asset.Name, "checksums.txt") {
checksumFileURL = asset.BrowserDownloadURL
}
for _, filenameOption := range fileNameOptions {
if strings.EqualFold(asset.Name, filenameOption) {
// We also want to know the asset name and download URL
rtn.Filename = asset.Name
rtn.URL = asset.BrowserDownloadURL
if strings.HasSuffix(asset.Name, ".tar.gz") {
rtn.FileExt = ".tar.gz"
} else {
rtn.FileExt = filepath.Ext(asset.Name)
}
}
}
}
if checksumFileURL == "" {
return nil, errors.New("unable to find checksum file in Github release")
}
if rtn.URL == "" || rtn.Filename == "" {
return nil, errors.New("unable to find binary in Github release")
}
// Download the checksum file
checksumResp, err := http.Get(checksumFileURL)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch checksum file")
}
defer func() { _ = checksumResp.Body.Close() }()
if checksumResp.StatusCode != http.StatusOK {
return nil, errors.Newf("Unexpected response status code for checksum file: %s", checksumResp.Status)
}
// Read the checksum file line by line
scanner := bufio.NewScanner(checksumResp.Body)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasSuffix(line, rtn.Filename) {
checksumStr := strings.Split(line, " ")[0]
checksum, err := hex.DecodeString(checksumStr)
if err != nil {
return nil, errors.Wrap(err, "unable to decode checksum")
}
rtn.Checksum = checksum
return rtn, nil
}
}
return nil, errors.New("unable to find checksum for asset file in checksum file")
}
func downloadLatestGithubRelease(org, repo, os, arch string) (pathToFile string, rtnErr error) {
// Find the latest release
release, err := getGithubRelease(org, repo, os, arch)
if err != nil {
return "", err
}
// Create a cache dir for the download cache for this specific OS and architecture pair
cacheDir, err := osPkg.UserCacheDir()
if err != nil {
return "", errors.Wrap(err, "user cache dir")
}
path := filepath.Join(cacheDir, "encore-build-cache", "github-releases", org, repo, os, arch)
err = osPkg.MkdirAll(path, 0755)
if err != nil {
return "", errors.Wrap(err, "failed to make cache dir")
}
downloadFileName := fmt.Sprintf("%s-%s%s", release.Version, hex.EncodeToString(release.Checksum), release.FileExt)
downloadPath := filepath.Join(path, downloadFileName)
// Check if the file already exists
if _, err := osPkg.Stat(downloadPath); err == nil {
return downloadPath, nil
} else if !osPkg.IsNotExist(err) {
return "", errors.Wrap(err, "failed to stat existing download file")
}
// Now download the file
downloadResp, err := http.Get(release.URL)
if err != nil {
return "", errors.Wrap(err, "unable to fetch release file")
}
defer func() { _ = downloadResp.Body.Close() }()
if downloadResp.StatusCode != http.StatusOK {
return "", errors.Newf("Unexpected response status code for release file: %s", downloadResp.Status)
}
// Create the file
downloadFile, err := osPkg.Create(downloadPath)
defer func() {
_ = downloadFile.Close()
if rtnErr != nil {
_ = osPkg.Remove(downloadPath) // delete any partially written file
}
}()
_, err = io.Copy(downloadFile, downloadResp.Body)
if err != nil {
return "", errors.Wrap(err, "unable to download release file")
}
// Now checksum the file
if _, err := downloadFile.Seek(0, 0); err != nil {
return "", errors.Wrap(err, "unable to seek to start of release file")
}
checksum, err := checksumFile(downloadFile)
if err != nil {
return "", errors.Wrap(err, "unable to checksum release file")
}
// Check the checksum
if !bytes.Equal(checksum, release.Checksum) {
return "", errors.Newf("checksum of downloaded file (%q) does not match expected checksum (%q)", hex.EncodeToString(checksum), hex.EncodeToString(release.Checksum))
}
return downloadPath, nil
}
func checksumFile(file *osPkg.File) ([]byte, error) {
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return nil, errors.Wrap(err, "unable to checksum file")
}
return hash.Sum(nil), nil
}
func extractArchive(pathToArchive string, targetDir string) error {
// Create the target dir
if err := osPkg.MkdirAll(targetDir, 0755); err != nil {
return errors.Wrap(err, "failed to create target dir")
}
// Extract the archive
cmd := exec.Command("tar", "-xzf", pathToArchive, "--strip-components", "1", "-C", targetDir)
// nosemgrep
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Wrapf(err, "failed to extract archive: %s", out)
}
return nil
}
func TarGzip(srcDirectory string, tarFile string) error {
// Create the tar.gz file from the src directory
cmd := exec.Command("tar", "-czf", tarFile, "-C", srcDirectory, ".")
// nosemgrep
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Wrapf(err, "failed to create tar.gz: %s", out)
}
return nil
}
func copyDir(src, dst string) error {
cmd := exec.Command("cp", "-r", src+"/", dst)
// nosemgrep
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Wrapf(err, "failed to copy dir: %s", out)
}
return nil
}