release.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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. // fmt.Println(releaseURL)
  118. // if err != nil {
  119. // return err
  120. // }
  121. // zipFile := filepath.Join(porterDir, "portersrv_latest.zip")
  122. // err = downloadToFile(releaseURL, zipFile)
  123. // if err != nil {
  124. // return err
  125. // }
  126. // err = unzipToDir(zipFile, porterDir)
  127. // if err != nil {
  128. // return err
  129. // }
  130. // staticZipFile := filepath.Join(porterDir, "static_latest.zip")
  131. // err = downloadToFile(staticReleaseURL, staticZipFile)
  132. // if err != nil {
  133. // return err
  134. // }
  135. // staticDir := filepath.Join(porterDir, "static")
  136. // err = unzipToDir(staticZipFile, staticDir)
  137. // return err
  138. // }
  139. func (z *ZIPReleaseGetter) downloadToFile(url string) error {
  140. // Get the data
  141. resp, err := http.Get(url)
  142. if err != nil {
  143. return err
  144. }
  145. defer resp.Body.Close()
  146. // Create the file
  147. out, err := os.Create(filepath.Join(z.ZipFolderDest, z.ZipName))
  148. if err != nil {
  149. return err
  150. }
  151. defer out.Close()
  152. // Write the body to file
  153. _, err = io.Copy(out, resp.Body)
  154. return err
  155. }
  156. func (z *ZIPReleaseGetter) unzipToDir() error {
  157. r, err := zip.OpenReader(filepath.Join(z.ZipFolderDest, z.ZipName))
  158. if err != nil {
  159. return err
  160. }
  161. defer r.Close()
  162. for _, f := range r.File {
  163. // Store filename/path for returning and using later on
  164. fpath := filepath.Join(z.AssetFolderDest, f.Name)
  165. // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
  166. if !strings.HasPrefix(fpath, filepath.Clean(z.AssetFolderDest)+string(os.PathSeparator)) {
  167. return fmt.Errorf("%s: illegal file path", fpath)
  168. }
  169. if f.FileInfo().IsDir() {
  170. // Make Folder
  171. os.MkdirAll(fpath, os.ModePerm)
  172. continue
  173. }
  174. // Make File
  175. if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
  176. return err
  177. }
  178. outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
  179. if err != nil {
  180. return err
  181. }
  182. rc, err := f.Open()
  183. if err != nil {
  184. return err
  185. }
  186. _, err = io.Copy(outFile, rc)
  187. // Close the file without defer to close before next iteration of loop
  188. outFile.Close()
  189. rc.Close()
  190. if err != nil {
  191. return err
  192. }
  193. }
  194. return nil
  195. }