release.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package github
  2. import (
  3. "archive/zip"
  4. "context"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "runtime"
  12. "strings"
  13. "github.com/google/go-github/v41/github"
  14. )
  15. // ZIPReleaseGetter retrieves a release from Github in ZIP format and downloads it
  16. // to a directory on host
  17. type ZIPReleaseGetter struct {
  18. // The name of the asset, i.e. "porter", "portersvr", "static"
  19. AssetName string
  20. // The host folder destination of the asset
  21. AssetFolderDest string
  22. // The host folder destination for the .zip file
  23. ZipFolderDest string
  24. // The name of the .zip file to download to
  25. ZipName string
  26. // The name of the Github entity whose repo is queried: i.e. "porter-dev"
  27. EntityID string
  28. // The name of the Github repo to get releases from
  29. RepoName string
  30. // If the asset is platform dependent
  31. IsPlatformDependent bool
  32. // The downloader/unzipper
  33. Downloader *ZIPDownloader
  34. }
  35. // GetLatestRelease downloads the latest .zip release from a given Github repository
  36. func (z *ZIPReleaseGetter) GetLatestRelease() error {
  37. releaseURL, err := z.getLatestReleaseDownloadURL()
  38. if err != nil {
  39. return err
  40. }
  41. return z.getReleaseFromURL(releaseURL)
  42. }
  43. // GetRelease downloads a specific .zip release from a given Github repository
  44. func (z *ZIPReleaseGetter) GetRelease(releaseTag string) error {
  45. releaseURL, err := z.getReleaseDownloadURL(releaseTag)
  46. fmt.Printf("getting release %s\n", releaseURL)
  47. if err != nil {
  48. return err
  49. }
  50. return z.getReleaseFromURL(releaseURL)
  51. }
  52. func (z *ZIPReleaseGetter) getReleaseFromURL(releaseURL string) error {
  53. fmt.Printf("getting release %s\n", releaseURL)
  54. err := z.Downloader.DownloadToFile(releaseURL)
  55. fmt.Printf("downloaded release %s to file %s\n", z.AssetName, filepath.Join(z.ZipFolderDest, z.ZipName))
  56. if err != nil {
  57. return err
  58. }
  59. fmt.Printf("unzipping %s to %s\n", z.AssetName, z.AssetFolderDest)
  60. err = z.Downloader.UnzipToDir()
  61. return err
  62. }
  63. // retrieves the download url for the latest release of an asset
  64. func (z *ZIPReleaseGetter) getLatestReleaseDownloadURL() (string, error) {
  65. client := github.NewClient(nil)
  66. rel, _, err := client.Repositories.GetLatestRelease(context.Background(), z.EntityID, z.RepoName)
  67. if err != nil {
  68. return "", err
  69. }
  70. re, err := z.getDownloadRegexp()
  71. if err != nil {
  72. return "", err
  73. }
  74. releaseURL := ""
  75. // iterate through the assets
  76. for _, asset := range rel.Assets {
  77. if downloadURL := asset.GetBrowserDownloadURL(); re.MatchString(downloadURL) {
  78. releaseURL = downloadURL
  79. }
  80. }
  81. return releaseURL, nil
  82. }
  83. func (z *ZIPReleaseGetter) getReleaseDownloadURL(releaseTag string) (string, error) {
  84. client := github.NewClient(nil)
  85. rel, _, err := client.Repositories.GetReleaseByTag(context.Background(), z.EntityID, z.RepoName, releaseTag)
  86. if err != nil {
  87. return "", fmt.Errorf("release %s does not exist", releaseTag)
  88. }
  89. re, err := z.getDownloadRegexp()
  90. if err != nil {
  91. return "", err
  92. }
  93. releaseURL := ""
  94. // iterate through the assets
  95. for _, asset := range rel.Assets {
  96. if downloadURL := asset.GetBrowserDownloadURL(); re.MatchString(downloadURL) {
  97. releaseURL = downloadURL
  98. }
  99. }
  100. return releaseURL, nil
  101. }
  102. func (z *ZIPReleaseGetter) getDownloadRegexp() (*regexp.Regexp, error) {
  103. if z.IsPlatformDependent {
  104. switch os := runtime.GOOS; os {
  105. case "darwin":
  106. return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*_Darwin_x86_64\.zip`, z.AssetName)), nil
  107. case "linux":
  108. return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*_Linux_x86_64\.zip`, z.AssetName)), nil
  109. default:
  110. return nil, fmt.Errorf("%s is not a supported platform for Porter binaries", os)
  111. }
  112. }
  113. return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*\.zip`, z.AssetName)), nil
  114. }
  115. type ZIPDownloader struct {
  116. ZipFolderDest string
  117. ZipName string
  118. AssetFolderDest string
  119. RemoveAfterDownload bool
  120. }
  121. func (z *ZIPDownloader) DownloadToFile(url string) error {
  122. // Get the data
  123. resp, err := http.Get(url)
  124. if err != nil {
  125. return err
  126. }
  127. defer resp.Body.Close()
  128. // Create the file
  129. out, err := os.Create(filepath.Join(z.ZipFolderDest, z.ZipName))
  130. if err != nil {
  131. return err
  132. }
  133. defer out.Close()
  134. // Write the body to file
  135. _, err = io.Copy(out, resp.Body)
  136. return err
  137. }
  138. func (z *ZIPDownloader) UnzipToDir() error {
  139. r, err := zip.OpenReader(filepath.Join(z.ZipFolderDest, z.ZipName))
  140. if err != nil {
  141. return err
  142. }
  143. defer r.Close()
  144. for _, f := range r.File {
  145. // Store filename/path for returning and using later on
  146. fpath := filepath.Join(z.AssetFolderDest, f.Name)
  147. // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
  148. if !strings.HasPrefix(fpath, filepath.Clean(z.AssetFolderDest)+string(os.PathSeparator)) {
  149. return fmt.Errorf("%s: illegal file path", fpath)
  150. }
  151. if f.FileInfo().IsDir() {
  152. // Make Folder
  153. os.MkdirAll(fpath, os.ModePerm)
  154. continue
  155. }
  156. // delete file if exists
  157. os.Remove(fpath)
  158. // Make File
  159. if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
  160. return err
  161. }
  162. outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
  163. if err != nil {
  164. return err
  165. }
  166. rc, err := f.Open()
  167. if err != nil {
  168. return err
  169. }
  170. _, err = io.Copy(outFile, rc)
  171. // Close the file without defer to close before next iteration of loop
  172. outFile.Close()
  173. rc.Close()
  174. if err != nil {
  175. return err
  176. }
  177. }
  178. if z.RemoveAfterDownload {
  179. os.Remove(filepath.Join(z.ZipFolderDest, z.ZipName))
  180. }
  181. return nil
  182. }