release.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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/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. }
  33. // GetLatestRelease downloads the latest .zip release from a given Github repository
  34. func (z *ZIPReleaseGetter) GetLatestRelease() error {
  35. releaseURL, err := z.getLatestReleaseDownloadURL()
  36. if err != nil {
  37. return err
  38. }
  39. return z.getReleaseFromURL(releaseURL)
  40. }
  41. // GetRelease downloads a specific .zip release from a given Github repository
  42. func (z *ZIPReleaseGetter) GetRelease(releaseTag string) error {
  43. releaseURL, err := z.getReleaseDownloadURL(releaseTag)
  44. fmt.Printf("getting release %s\n", releaseURL)
  45. if err != nil {
  46. return err
  47. }
  48. return z.getReleaseFromURL(releaseURL)
  49. }
  50. func (z *ZIPReleaseGetter) getReleaseFromURL(releaseURL string) error {
  51. fmt.Printf("getting release %s\n", releaseURL)
  52. err := z.downloadToFile(releaseURL)
  53. fmt.Printf("downloaded release %s to file %s\n", z.AssetName, filepath.Join(z.ZipFolderDest, z.ZipName))
  54. if err != nil {
  55. return err
  56. }
  57. fmt.Printf("unzipping %s to %s\n", z.AssetName, z.AssetFolderDest)
  58. err = z.unzipToDir()
  59. return err
  60. }
  61. // retrieves the download url for the latest release of an asset
  62. func (z *ZIPReleaseGetter) getLatestReleaseDownloadURL() (string, error) {
  63. client := github.NewClient(nil)
  64. rel, _, err := client.Repositories.GetLatestRelease(context.Background(), z.EntityID, z.RepoName)
  65. if err != nil {
  66. return "", err
  67. }
  68. re, err := z.getDownloadRegexp()
  69. if err != nil {
  70. return "", err
  71. }
  72. releaseURL := ""
  73. // iterate through the assets
  74. for _, asset := range rel.Assets {
  75. if downloadURL := asset.GetBrowserDownloadURL(); re.MatchString(downloadURL) {
  76. releaseURL = downloadURL
  77. }
  78. }
  79. return releaseURL, nil
  80. }
  81. func (z *ZIPReleaseGetter) getReleaseDownloadURL(releaseTag string) (string, error) {
  82. client := github.NewClient(nil)
  83. rel, _, err := client.Repositories.GetReleaseByTag(context.Background(), z.EntityID, z.RepoName, releaseTag)
  84. if err != nil {
  85. return "", fmt.Errorf("release %s does not exist", releaseTag)
  86. }
  87. re, err := z.getDownloadRegexp()
  88. if err != nil {
  89. return "", err
  90. }
  91. releaseURL := ""
  92. // iterate through the assets
  93. for _, asset := range rel.Assets {
  94. if downloadURL := asset.GetBrowserDownloadURL(); re.MatchString(downloadURL) {
  95. releaseURL = downloadURL
  96. }
  97. }
  98. return releaseURL, nil
  99. }
  100. func (z *ZIPReleaseGetter) getDownloadRegexp() (*regexp.Regexp, error) {
  101. if z.IsPlatformDependent {
  102. switch os := runtime.GOOS; os {
  103. case "darwin":
  104. return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*_Darwin_x86_64\.zip`, z.AssetName)), nil
  105. case "linux":
  106. return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*_Linux_x86_64\.zip`, z.AssetName)), nil
  107. default:
  108. return nil, fmt.Errorf("%s is not a supported platform for Porter binaries", os)
  109. }
  110. }
  111. return regexp.MustCompile(fmt.Sprintf(`(?i)%s_.*\.zip`, z.AssetName)), nil
  112. }
  113. // // DownloadLatestServerRelease retrieves the latest Porter server release from Github, unzips
  114. // // it, and adds the binary to the porter directory
  115. // func DownloadLatestServerRelease(porterDir string) error {
  116. // releaseURL, staticReleaseURL, err := getLatestReleaseDownloadURL()
  117. // if err != nil {
  118. // return err
  119. // }
  120. // zipFile := filepath.Join(porterDir, "portersrv_latest.zip")
  121. // err = downloadToFile(releaseURL, zipFile)
  122. // if err != nil {
  123. // return err
  124. // }
  125. // err = unzipToDir(zipFile, porterDir)
  126. // if err != nil {
  127. // return err
  128. // }
  129. // staticZipFile := filepath.Join(porterDir, "static_latest.zip")
  130. // err = downloadToFile(staticReleaseURL, staticZipFile)
  131. // if err != nil {
  132. // return err
  133. // }
  134. // staticDir := filepath.Join(porterDir, "static")
  135. // err = unzipToDir(staticZipFile, staticDir)
  136. // return err
  137. // }
  138. func (z *ZIPReleaseGetter) downloadToFile(url string) error {
  139. // Get the data
  140. resp, err := http.Get(url)
  141. if err != nil {
  142. return err
  143. }
  144. defer resp.Body.Close()
  145. // Create the file
  146. out, err := os.Create(filepath.Join(z.ZipFolderDest, z.ZipName))
  147. if err != nil {
  148. return err
  149. }
  150. defer out.Close()
  151. // Write the body to file
  152. _, err = io.Copy(out, resp.Body)
  153. return err
  154. }
  155. func (z *ZIPReleaseGetter) unzipToDir() error {
  156. r, err := zip.OpenReader(filepath.Join(z.ZipFolderDest, z.ZipName))
  157. if err != nil {
  158. return err
  159. }
  160. defer r.Close()
  161. for _, f := range r.File {
  162. // Store filename/path for returning and using later on
  163. fpath := filepath.Join(z.AssetFolderDest, f.Name)
  164. // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
  165. if !strings.HasPrefix(fpath, filepath.Clean(z.AssetFolderDest)+string(os.PathSeparator)) {
  166. return fmt.Errorf("%s: illegal file path", fpath)
  167. }
  168. if f.FileInfo().IsDir() {
  169. // Make Folder
  170. os.MkdirAll(fpath, os.ModePerm)
  171. continue
  172. }
  173. // delete file if exists
  174. os.Remove(fpath)
  175. // Make File
  176. if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
  177. return err
  178. }
  179. outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
  180. if err != nil {
  181. return err
  182. }
  183. rc, err := f.Open()
  184. if err != nil {
  185. return err
  186. }
  187. _, err = io.Copy(outFile, rc)
  188. // Close the file without defer to close before next iteration of loop
  189. outFile.Close()
  190. rc.Close()
  191. if err != nil {
  192. return err
  193. }
  194. }
  195. return nil
  196. }