| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- package github
- import (
- "archive/zip"
- "context"
- "fmt"
- "io"
- "net/http"
- "os"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
- "github.com/google/go-github/v41/github"
- )
- // ZIPReleaseGetter retrieves a release from Github in ZIP format and downloads it
- // to a directory on host
- type ZIPReleaseGetter struct {
- // The name of the asset, i.e. "porter", "portersvr", "static"
- AssetName string
- // The host folder destination of the asset
- AssetFolderDest string
- // The host folder destination for the .zip file
- ZipFolderDest string
- // The name of the .zip file to download to
- ZipName string
- // The name of the Github entity whose repo is queried: i.e. "porter-dev"
- EntityID string
- // The name of the Github repo to get releases from
- RepoName string
- // If the asset is platform dependent
- IsPlatformDependent bool
- // The downloader/unzipper
- Downloader *ZIPDownloader
- }
- // GetLatestRelease downloads the latest .zip release from a given Github repository
- func (z *ZIPReleaseGetter) GetLatestRelease() error {
- releaseURL, err := z.getLatestReleaseDownloadURL()
- if err != nil {
- return err
- }
- return z.getReleaseFromURL(releaseURL)
- }
- // GetRelease downloads a specific .zip release from a given Github repository
- func (z *ZIPReleaseGetter) GetRelease(releaseTag string) error {
- releaseURL, err := z.getReleaseDownloadURL(releaseTag)
- fmt.Printf("getting release %s\n", releaseURL)
- if err != nil {
- return err
- }
- return z.getReleaseFromURL(releaseURL)
- }
- func (z *ZIPReleaseGetter) getReleaseFromURL(releaseURL string) error {
- fmt.Printf("getting release %s\n", releaseURL)
- err := z.Downloader.DownloadToFile(releaseURL)
- fmt.Printf("downloaded release %s to file %s\n", z.AssetName, filepath.Join(z.ZipFolderDest, z.ZipName))
- if err != nil {
- return err
- }
- fmt.Printf("unzipping %s to %s\n", z.AssetName, z.AssetFolderDest)
- err = z.Downloader.UnzipToDir()
- return err
- }
- // retrieves the download url for the latest release of an asset
- func (z *ZIPReleaseGetter) getLatestReleaseDownloadURL() (string, error) {
- client := github.NewClient(nil)
- rel, _, err := client.Repositories.GetLatestRelease(context.Background(), z.EntityID, z.RepoName)
- if err != nil {
- return "", err
- }
- re, err := z.getDownloadRegexp()
- if err != nil {
- return "", err
- }
- releaseURL := ""
- // iterate through the assets
- for _, asset := range rel.Assets {
- if downloadURL := asset.GetBrowserDownloadURL(); re.MatchString(downloadURL) {
- releaseURL = downloadURL
- }
- }
- return releaseURL, nil
- }
- func (z *ZIPReleaseGetter) getReleaseDownloadURL(releaseTag string) (string, error) {
- client := github.NewClient(nil)
- rel, _, err := client.Repositories.GetReleaseByTag(context.Background(), z.EntityID, z.RepoName, releaseTag)
- if err != nil {
- return "", fmt.Errorf("release %s does not exist", releaseTag)
- }
- re, err := z.getDownloadRegexp()
- if err != nil {
- return "", err
- }
- releaseURL := ""
- // iterate through the assets
- for _, asset := range rel.Assets {
- if downloadURL := asset.GetBrowserDownloadURL(); re.MatchString(downloadURL) {
- releaseURL = downloadURL
- }
- }
- return releaseURL, nil
- }
- func (z *ZIPReleaseGetter) getDownloadRegexp() (*regexp.Regexp, error) {
- if z.IsPlatformDependent {
- switch os := runtime.GOOS; os {
- case "darwin":
- return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*_Darwin_x86_64\.zip`, z.AssetName)), nil
- case "linux":
- return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*_Linux_x86_64\.zip`, z.AssetName)), nil
- default:
- return nil, fmt.Errorf("%s is not a supported platform for Porter binaries", os)
- }
- }
- return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*\.zip`, z.AssetName)), nil
- }
- type ZIPDownloader struct {
- ZipFolderDest string
- ZipName string
- AssetFolderDest string
- RemoveAfterDownload bool
- }
- func (z *ZIPDownloader) DownloadToFile(url string) error {
- // Get the data
- resp, err := http.Get(url)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- // Create the file
- out, err := os.Create(filepath.Join(z.ZipFolderDest, z.ZipName))
- if err != nil {
- return err
- }
- defer out.Close()
- // Write the body to file
- _, err = io.Copy(out, resp.Body)
- return err
- }
- func (z *ZIPDownloader) UnzipToDir() error {
- r, err := zip.OpenReader(filepath.Join(z.ZipFolderDest, z.ZipName))
- if err != nil {
- return err
- }
- defer r.Close()
- for _, f := range r.File {
- // Store filename/path for returning and using later on
- fpath := filepath.Join(z.AssetFolderDest, f.Name)
- // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
- if !strings.HasPrefix(fpath, filepath.Clean(z.AssetFolderDest)+string(os.PathSeparator)) {
- return fmt.Errorf("%s: illegal file path", fpath)
- }
- if f.FileInfo().IsDir() {
- // Make Folder
- os.MkdirAll(fpath, os.ModePerm)
- continue
- }
- // delete file if exists
- os.Remove(fpath)
- // Make File
- if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
- return err
- }
- outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
- if err != nil {
- return err
- }
- rc, err := f.Open()
- if err != nil {
- return err
- }
- _, err = io.Copy(outFile, rc)
- // Close the file without defer to close before next iteration of loop
- outFile.Close()
- rc.Close()
- if err != nil {
- return err
- }
- }
- if z.RemoveAfterDownload {
- os.Remove(filepath.Join(z.ZipFolderDest, z.ZipName))
- }
- return nil
- }
|