Ctrl K
ComunidadGuías…

pkg/make-release/utils.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}