b2d.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package utils
  2. import (
  3. //"encoding/json"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "net"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. "time"
  13. "github.com/docker/machine/log"
  14. )
  15. const (
  16. timeout = time.Second * 5
  17. )
  18. func defaultTimeout(network, addr string) (net.Conn, error) {
  19. return net.DialTimeout(network, addr, timeout)
  20. }
  21. func getClient() *http.Client {
  22. transport := http.Transport{
  23. DisableKeepAlives: true,
  24. Proxy: http.ProxyFromEnvironment,
  25. Dial: defaultTimeout,
  26. }
  27. client := http.Client{
  28. Transport: &transport,
  29. }
  30. return &client
  31. }
  32. type B2dUtils struct {
  33. isoFilename string
  34. commonIsoPath string
  35. imgCachePath string
  36. githubApiBaseUrl string
  37. githubBaseUrl string
  38. }
  39. func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils {
  40. defaultBaseApiUrl := "https://api.github.com"
  41. defaultBaseUrl := "https://github.com"
  42. imgCachePath := GetMachineCacheDir()
  43. if githubApiBaseUrl == "" {
  44. githubApiBaseUrl = defaultBaseApiUrl
  45. }
  46. if githubBaseUrl == "" {
  47. githubBaseUrl = defaultBaseUrl
  48. }
  49. return &B2dUtils{
  50. isoFilename: isoFilename,
  51. imgCachePath: GetMachineCacheDir(),
  52. commonIsoPath: filepath.Join(imgCachePath, isoFilename),
  53. githubApiBaseUrl: githubApiBaseUrl,
  54. githubBaseUrl: githubBaseUrl,
  55. }
  56. }
  57. // Get the latest boot2docker release tag name (e.g. "v0.6.0").
  58. // FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests
  59. func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) {
  60. //client := getClient()
  61. //apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl)
  62. //rsp, err := client.Get(apiUrl)
  63. //if err != nil {
  64. // return "", err
  65. //}
  66. //defer rsp.Body.Close()
  67. //var t []struct {
  68. // TagName string `json:"tag_name"`
  69. // PreRelease bool `json:"prerelease"`
  70. //}
  71. //if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil {
  72. // return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err)
  73. //}
  74. //if len(t) == 0 {
  75. // return "", fmt.Errorf("no releases found")
  76. //}
  77. //// find the latest "released" release (i.e. not pre-release)
  78. //isoUrl := ""
  79. //for _, r := range t {
  80. // if !r.PreRelease {
  81. // tag := r.TagName
  82. // isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag)
  83. // break
  84. // }
  85. //}
  86. //return isoUrl, nil
  87. // TODO: once we decide on the final versioning and location we will
  88. // enable the above "check for latest"
  89. u := fmt.Sprintf("https://s3.amazonaws.com/docker-mcn/public/b2d-next/%s", b.isoFilename)
  90. return u, nil
  91. }
  92. func removeFileIfExists(name string) error {
  93. if _, err := os.Stat(name); err == nil {
  94. if err := os.Remove(name); err != nil {
  95. log.Fatalf("Error removing temporary download file: %s", err)
  96. }
  97. }
  98. return nil
  99. }
  100. // Download boot2docker ISO image for the given tag and save it at dest.
  101. func (b *B2dUtils) DownloadISO(dir, file, isoUrl string) error {
  102. u, err := url.Parse(isoUrl)
  103. var src io.ReadCloser
  104. if u.Scheme == "file" || u.Scheme == "" {
  105. s, err := os.Open(u.Path)
  106. if err != nil {
  107. return err
  108. }
  109. src = s
  110. } else {
  111. client := getClient()
  112. s, err := client.Get(isoUrl)
  113. if err != nil {
  114. return err
  115. }
  116. src = s.Body
  117. }
  118. defer src.Close()
  119. // Download to a temp file first then rename it to avoid partial download.
  120. f, err := ioutil.TempFile(dir, file+".tmp")
  121. if err != nil {
  122. return err
  123. }
  124. defer func() {
  125. if err := removeFileIfExists(f.Name()); err != nil {
  126. log.Fatalf("Error removing file: %s", err)
  127. }
  128. }()
  129. if _, err := io.Copy(f, src); err != nil {
  130. // TODO: display download progress?
  131. return err
  132. }
  133. if err := f.Close(); err != nil {
  134. return err
  135. }
  136. // Dest is the final path of the boot2docker.iso file.
  137. dest := filepath.Join(dir, file)
  138. // Windows can't rename in place, so remove the old file before
  139. // renaming the temporary downloaded file.
  140. if err := removeFileIfExists(dest); err != nil {
  141. return err
  142. }
  143. if err := os.Rename(f.Name(), dest); err != nil {
  144. return err
  145. }
  146. return nil
  147. }
  148. func (b *B2dUtils) DownloadLatestBoot2Docker() error {
  149. latestReleaseUrl, err := b.GetLatestBoot2DockerReleaseURL()
  150. if err != nil {
  151. return err
  152. }
  153. return b.DownloadISOFromURL(latestReleaseUrl)
  154. }
  155. func (b *B2dUtils) DownloadISOFromURL(latestReleaseUrl string) error {
  156. log.Infof("Downloading %s to %s...", latestReleaseUrl, b.commonIsoPath)
  157. if err := b.DownloadISO(b.imgCachePath, b.isoFilename, latestReleaseUrl); err != nil {
  158. return err
  159. }
  160. return nil
  161. }
  162. func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error {
  163. machinesDir := GetMachineDir()
  164. machineIsoPath := filepath.Join(machinesDir, machineName, b.isoFilename)
  165. // just in case the cache dir has been manually deleted,
  166. // check for it and recreate it if it's gone
  167. if _, err := os.Stat(b.imgCachePath); os.IsNotExist(err) {
  168. log.Infof("Image cache does not exist, creating it at %s...", b.imgCachePath)
  169. if err := os.Mkdir(b.imgCachePath, 0700); err != nil {
  170. return err
  171. }
  172. }
  173. // By default just copy the existing "cached" iso to
  174. // the machine's directory...
  175. if isoURL == "" {
  176. if err := b.copyDefaultIsoToMachine(machineIsoPath); err != nil {
  177. return err
  178. }
  179. } else {
  180. // But if ISO is specified go get it directly
  181. log.Infof("Downloading %s from %s...", b.isoFilename, isoURL)
  182. if err := b.DownloadISO(filepath.Join(machinesDir, machineName), b.isoFilename, isoURL); err != nil {
  183. return err
  184. }
  185. }
  186. return nil
  187. }
  188. func (b *B2dUtils) copyDefaultIsoToMachine(machineIsoPath string) error {
  189. if _, err := os.Stat(b.commonIsoPath); os.IsNotExist(err) {
  190. log.Info("No default boot2docker iso found locally, downloading the latest release...")
  191. if err := b.DownloadLatestBoot2Docker(); err != nil {
  192. return err
  193. }
  194. }
  195. if err := CopyFile(b.commonIsoPath, machineIsoPath); err != nil {
  196. return err
  197. }
  198. return nil
  199. }