forked from machine-drivers/machine
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request machine-drivers#7 from praveenkumar/fix_diskurl
Add diskurl to fetch the disk images
- Loading branch information
Showing
1 changed file
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
package mcnutils | ||
|
||
import ( | ||
"fmt" | ||
"github.com/code-ready/machine/libmachine/log" | ||
"io" | ||
"io/ioutil" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
const ( | ||
defaultDiskFilename = "crc" | ||
) | ||
|
||
|
||
func defaultTimeout(network, addr string) (net.Conn, error) { | ||
return net.Dial(network, addr) | ||
} | ||
|
||
func getClient() *http.Client { | ||
transport := http.Transport{ | ||
DisableKeepAlives: true, | ||
Proxy: http.ProxyFromEnvironment, | ||
Dial: defaultTimeout, | ||
} | ||
|
||
return &http.Client{ | ||
Transport: &transport, | ||
} | ||
} | ||
|
||
// releaseGetter is a client that gets release information of a product and downloads it. | ||
type releaseGetter interface { | ||
// filename returns filename of the product. | ||
filename() string | ||
|
||
// download downloads a file from the given dlURL and saves it under dir. | ||
download(dir, file, dlURL string) error | ||
} | ||
|
||
// disk is an disk path. | ||
type disk interface { | ||
// path returns the path of the Disk. | ||
path() string | ||
// exists reports whether the Disk exists. | ||
exists() bool | ||
} | ||
|
||
// crcDisk represents a CRC disk Image. It implements the Disk interface. | ||
type crcDisk struct { | ||
// path of Disk ISO | ||
commonDiskPath string | ||
} | ||
|
||
func (b *crcDisk) path() string { | ||
if b == nil { | ||
return "" | ||
} | ||
return b.commonDiskPath | ||
} | ||
|
||
func (b *crcDisk) exists() bool { | ||
if b == nil { | ||
return false | ||
} | ||
|
||
_, err := os.Stat(b.commonDiskPath) | ||
return !os.IsNotExist(err) | ||
} | ||
|
||
func removeFileIfExists(name string) error { | ||
if _, err := os.Stat(name); err == nil { | ||
if err := os.Remove(name); err != nil { | ||
return fmt.Errorf("Error removing temporary download file: %s", err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// crcReleaseGetter implements the releaseGetter interface for getting the Disk image. | ||
type crcReleaseGetter struct { | ||
diskFilename string | ||
} | ||
|
||
func (b *crcReleaseGetter) filename() string { | ||
if b == nil { | ||
return "" | ||
} | ||
return b.diskFilename | ||
} | ||
|
||
func (*crcReleaseGetter) download(dir, file, isoURL string) error { | ||
u, err := url.Parse(isoURL) | ||
|
||
var src io.ReadCloser | ||
if u.Scheme == "file" || u.Scheme == "" { | ||
s, err := os.Open(u.Path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
src = s | ||
} else { | ||
client := getClient() | ||
s, err := client.Get(isoURL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
src = &ReaderWithProgress{ | ||
ReadCloser: s.Body, | ||
out: os.Stdout, | ||
expectedLength: s.ContentLength, | ||
} | ||
} | ||
|
||
defer src.Close() | ||
|
||
// Download to a temp file first then rename it to avoid partial download. | ||
f, err := ioutil.TempFile(dir, file+".tmp") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer func() { | ||
if err := removeFileIfExists(f.Name()); err != nil { | ||
log.Warnf("Error removing file: %s", err) | ||
} | ||
}() | ||
|
||
if _, err := io.Copy(f, src); err != nil { | ||
return err | ||
} | ||
|
||
if err := f.Close(); err != nil { | ||
return err | ||
} | ||
|
||
// Dest is the final path of the disk image | ||
dest := filepath.Join(dir, file) | ||
|
||
// Windows can't rename in place, so remove the old file before | ||
// renaming the temporary downloaded file. | ||
if err := removeFileIfExists(dest); err != nil { | ||
return err | ||
} | ||
|
||
return os.Rename(f.Name(), dest) | ||
} | ||
|
||
|
||
type B2dUtils struct { | ||
releaseGetter | ||
disk | ||
storePath string | ||
imgCachePath string | ||
} | ||
|
||
func NewB2dUtils(storePath string) *B2dUtils { | ||
imgCachePath := filepath.Join(storePath, "cache") | ||
|
||
return &B2dUtils{ | ||
releaseGetter: &crcReleaseGetter{diskFilename: defaultDiskFilename}, | ||
disk: &crcDisk{ | ||
commonDiskPath: filepath.Join(imgCachePath, defaultDiskFilename), | ||
}, | ||
storePath: storePath, | ||
imgCachePath: imgCachePath, | ||
} | ||
} | ||
|
||
// DownloadISO downloads boot2docker ISO image for the given tag and save it at dest. | ||
func (b *B2dUtils) DownloadDisk(dir, file, isoURL string) error { | ||
log.Infof("Downloading %s from %s...", b.path(), isoURL) | ||
return b.download(dir, file, isoURL) | ||
} | ||
|
||
type ReaderWithProgress struct { | ||
io.ReadCloser | ||
out io.Writer | ||
bytesTransferred int64 | ||
expectedLength int64 | ||
nextPercentToPrint int64 | ||
} | ||
|
||
func (r *ReaderWithProgress) Read(p []byte) (int, error) { | ||
n, err := r.ReadCloser.Read(p) | ||
|
||
if n > 0 { | ||
r.bytesTransferred += int64(n) | ||
percentage := r.bytesTransferred * 100 / r.expectedLength | ||
|
||
for percentage >= r.nextPercentToPrint { | ||
if r.nextPercentToPrint%10 == 0 { | ||
fmt.Fprintf(r.out, "%d%%", r.nextPercentToPrint) | ||
} else if r.nextPercentToPrint%2 == 0 { | ||
fmt.Fprint(r.out, ".") | ||
} | ||
r.nextPercentToPrint += 2 | ||
} | ||
} | ||
|
||
return n, err | ||
} | ||
|
||
func (r *ReaderWithProgress) Close() error { | ||
fmt.Fprintln(r.out) | ||
return r.ReadCloser.Close() | ||
} | ||
|
||
func (b *B2dUtils) CopyDiskToMachineDir(diskURL, machineName string) error { | ||
// TODO: This is a bit off-color. | ||
machineDir := filepath.Join(b.storePath, "machines", machineName) | ||
machineIsoPath := filepath.Join(machineDir, b.filename()) | ||
|
||
// By default just copy the existing "cached" iso to the machine's directory... | ||
if diskURL == "" { | ||
log.Infof("Copying %s to %s...", b.path(), machineIsoPath) | ||
return CopyFile(b.path(), machineIsoPath) | ||
} | ||
|
||
return b.DownloadDisk(machineDir, b.filename(), diskURL) | ||
} |