The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/2728
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 524e330fb9670f71be23e2b2bc05647853ff1cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 15 Dec 2016 18:20:08 -0500 Subject: [PATCH 1/5] daemon: Common codepath for http client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/daemon.go | 40 ++++++++++++++-------------------------- lxd/images.go | 14 ++------------ 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/lxd/daemon.go b/lxd/daemon.go index 3d1c85c..4687c8c 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -107,10 +107,10 @@ type Command struct { patch func(d *Daemon, r *http.Request) Response } -func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) { +func (d *Daemon) httpClient(certificate string) (*http.Client, error) { var err error - var cert *x509.Certificate + if certificate != "" { certBlock, _ := pem.Decode([]byte(certificate)) if certBlock == nil { @@ -139,6 +139,12 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err Transport: tr, } + return &myhttp, nil +} + +func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) { + var err error + req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err @@ -146,6 +152,11 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err req.Header.Set("User-Agent", shared.UserAgent) + myhttp, err := d.httpClient(certificate) + if err != nil { + return nil, err + } + r, err := myhttp.Do(req) if err != nil { return nil, err @@ -166,34 +177,11 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, error) { var err error - var cert *x509.Certificate - if certificate != "" { - certBlock, _ := pem.Decode([]byte(certificate)) - if certBlock == nil { - return nil, fmt.Errorf("Invalid certificate") - } - - cert, err = x509.ParseCertificate(certBlock.Bytes) - if err != nil { - return nil, err - } - } - - tlsConfig, err := shared.GetTLSConfig("", "", "", cert) + myhttp, err := d.httpClient(certificate) if err != nil { return nil, err } - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - Dial: shared.RFC3493Dialer, - Proxy: d.proxy, - DisableKeepAlives: true, - } - myhttp := http.Client{ - Transport: tr, - } - req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err diff --git a/lxd/images.go b/lxd/images.go index dfa2d5c..291789f 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -375,22 +375,12 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error { return fmt.Errorf("Missing URL") } - // Resolve the image URL - tlsConfig, err := shared.GetTLSConfig("", "", "", nil) + myhttp, err := d.httpClient("") if err != nil { return err } - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - Dial: shared.RFC3493Dialer, - Proxy: d.proxy, - } - - myhttp := http.Client{ - Transport: tr, - } - + // Resolve the image URL head, err := http.NewRequest("HEAD", req.Source["url"], nil) if err != nil { return err From c3c611bceb1aa85d2d194c4debe286185e917e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 15 Dec 2016 17:18:44 -0500 Subject: [PATCH 2/5] shared: Give IO progress tracker its own package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- client.go | 9 ++-- lxd/daemon_images.go | 5 +- lxd/storage.go | 9 ++-- shared/ioprogress/reader.go | 23 +++++++++ shared/ioprogress/tracker.go | 76 ++++++++++++++++++++++++++++++ shared/ioprogress/writer.go | 23 +++++++++ shared/simplestreams.go | 6 ++- shared/util.go | 108 ------------------------------------------- 8 files changed, 139 insertions(+), 120 deletions(-) create mode 100644 shared/ioprogress/reader.go create mode 100644 shared/ioprogress/tracker.go create mode 100644 shared/ioprogress/writer.go diff --git a/client.go b/client.go index e931216..f7eb6af 100644 --- a/client.go +++ b/client.go @@ -24,6 +24,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/ioprogress" ) // Client can talk to a LXD daemon. @@ -1028,9 +1029,9 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str return "", err } - progress := &shared.ProgressReader{ + progress := &ioprogress.ProgressReader{ ReadCloser: body, - Tracker: &shared.ProgressTracker{ + Tracker: &ioprogress.ProgressTracker{ Length: size, Handler: progressHandler, }, @@ -1050,9 +1051,9 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str return "", err } - progress := &shared.ProgressReader{ + progress := &ioprogress.ProgressReader{ ReadCloser: fImage, - Tracker: &shared.ProgressTracker{ + Tracker: &ioprogress.ProgressTracker{ Length: stat.Size(), Handler: progressHandler, }, diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go index f841a6d..6ce6799 100644 --- a/lxd/daemon_images.go +++ b/lxd/daemon_images.go @@ -16,6 +16,7 @@ import ( "gopkg.in/yaml.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/ioprogress" log "gopkg.in/inconshreveable/log15.v2" ) @@ -359,9 +360,9 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce ctype = "application/octet-stream" } - body := &shared.ProgressReader{ + body := &ioprogress.ProgressReader{ ReadCloser: raw.Body, - Tracker: &shared.ProgressTracker{ + Tracker: &ioprogress.ProgressTracker{ Length: raw.ContentLength, Handler: progress, }, diff --git a/lxd/storage.go b/lxd/storage.go index 2ae706d..3edf294 100644 --- a/lxd/storage.go +++ b/lxd/storage.go @@ -14,6 +14,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/ioprogress" "github.com/lxc/lxd/shared/logging" log "gopkg.in/inconshreveable/log15.v2" @@ -833,9 +834,9 @@ func StorageProgressReader(op *operation, key string, description string) func(i progressWrapperRender(op, key, description, progressInt, speedInt) } - readPipe := &shared.ProgressReader{ + readPipe := &ioprogress.ProgressReader{ ReadCloser: reader, - Tracker: &shared.ProgressTracker{ + Tracker: &ioprogress.ProgressTracker{ Handler: progress, }, } @@ -854,9 +855,9 @@ func StorageProgressWriter(op *operation, key string, description string) func(i progressWrapperRender(op, key, description, progressInt, speedInt) } - writePipe := &shared.ProgressWriter{ + writePipe := &ioprogress.ProgressWriter{ WriteCloser: writer, - Tracker: &shared.ProgressTracker{ + Tracker: &ioprogress.ProgressTracker{ Handler: progress, }, } diff --git a/shared/ioprogress/reader.go b/shared/ioprogress/reader.go new file mode 100644 index 0000000..262aa40 --- /dev/null +++ b/shared/ioprogress/reader.go @@ -0,0 +1,23 @@ +package ioprogress + +import ( + "io" +) + +type ProgressReader struct { + io.ReadCloser + Tracker *ProgressTracker +} + +func (pt *ProgressReader) Read(p []byte) (int, error) { + // Do normal reader tasks + n, err := pt.ReadCloser.Read(p) + + // Do the actual progress tracking + if pt.Tracker != nil { + pt.Tracker.total += int64(n) + pt.Tracker.Update(n) + } + + return n, err +} diff --git a/shared/ioprogress/tracker.go b/shared/ioprogress/tracker.go new file mode 100644 index 0000000..78c730b --- /dev/null +++ b/shared/ioprogress/tracker.go @@ -0,0 +1,76 @@ +package ioprogress + +import ( + "time" +) + +type ProgressTracker struct { + Length int64 + Handler func(int64, int64) + + percentage float64 + total int64 + start *time.Time + last *time.Time +} + +func (pt *ProgressTracker) Update(n int) { + // Skip the rest if no handler attached + if pt.Handler == nil { + return + } + + // Initialize start time if needed + if pt.start == nil { + cur := time.Now() + pt.start = &cur + pt.last = pt.start + } + + // Skip if no data to count + if n <= 0 { + return + } + + // Update interval handling + var percentage float64 + if pt.Length > 0 { + // If running in relative mode, check that we increased by at least 1% + percentage = float64(pt.total) / float64(pt.Length) * float64(100) + if percentage-pt.percentage < 0.9 { + return + } + } else { + // If running in absolute mode, check that at least a second elapsed + interval := time.Since(*pt.last).Seconds() + if interval < 1 { + return + } + } + + // Determine speed + speedInt := int64(0) + duration := time.Since(*pt.start).Seconds() + if duration > 0 { + speed := float64(pt.total) / duration + speedInt = int64(speed) + } + + // Determine progress + progressInt := int64(0) + if pt.Length > 0 { + pt.percentage = percentage + progressInt = int64(1 - (int(percentage) % 1) + int(percentage)) + if progressInt > 100 { + progressInt = 100 + } + } else { + progressInt = pt.total + + // Update timestamp + cur := time.Now() + pt.last = &cur + } + + pt.Handler(progressInt, speedInt) +} diff --git a/shared/ioprogress/writer.go b/shared/ioprogress/writer.go new file mode 100644 index 0000000..708911b --- /dev/null +++ b/shared/ioprogress/writer.go @@ -0,0 +1,23 @@ +package ioprogress + +import ( + "io" +) + +type ProgressWriter struct { + io.WriteCloser + Tracker *ProgressTracker +} + +func (pt *ProgressWriter) Write(p []byte) (int, error) { + // Do normal writer tasks + n, err := pt.WriteCloser.Write(p) + + // Do the actual progress tracking + if pt.Tracker != nil { + pt.Tracker.total += int64(n) + pt.Tracker.Update(n) + } + + return n, err +} diff --git a/shared/simplestreams.go b/shared/simplestreams.go index 5bc9ab1..8f1ccfa 100644 --- a/shared/simplestreams.go +++ b/shared/simplestreams.go @@ -13,6 +13,8 @@ import ( "sort" "strings" "time" + + "github.com/lxc/lxd/shared/ioprogress" ) type ssSortImage []ImageInfo @@ -535,9 +537,9 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path) } - body := &ProgressReader{ + body := &ioprogress.ProgressReader{ ReadCloser: resp.Body, - Tracker: &ProgressTracker{ + Tracker: &ioprogress.ProgressTracker{ Length: resp.ContentLength, Handler: progress, }, diff --git a/shared/util.go b/shared/util.go index 8651c9d..cb28ba3 100644 --- a/shared/util.go +++ b/shared/util.go @@ -19,7 +19,6 @@ import ( "regexp" "strconv" "strings" - "time" ) const SnapshotDelimiter = "/" @@ -738,113 +737,6 @@ func RemoveDuplicatesFromString(s string, sep string) string { return s } -type ProgressTracker struct { - Length int64 - Handler func(int64, int64) - - percentage float64 - total int64 - start *time.Time - last *time.Time -} - -func (pt *ProgressTracker) Update(n int) { - // Skip the rest if no handler attached - if pt.Handler == nil { - return - } - - // Initialize start time if needed - if pt.start == nil { - cur := time.Now() - pt.start = &cur - pt.last = pt.start - } - - // Skip if no data to count - if n <= 0 { - return - } - - // Update interval handling - var percentage float64 - if pt.Length > 0 { - // If running in relative mode, check that we increased by at least 1% - percentage = float64(pt.total) / float64(pt.Length) * float64(100) - if percentage-pt.percentage < 0.9 { - return - } - } else { - // If running in absolute mode, check that at least a second elapsed - interval := time.Since(*pt.last).Seconds() - if interval < 1 { - return - } - } - - // Determine speed - speedInt := int64(0) - duration := time.Since(*pt.start).Seconds() - if duration > 0 { - speed := float64(pt.total) / duration - speedInt = int64(speed) - } - - // Determine progress - progressInt := int64(0) - if pt.Length > 0 { - pt.percentage = percentage - progressInt = int64(1 - (int(percentage) % 1) + int(percentage)) - if progressInt > 100 { - progressInt = 100 - } - } else { - progressInt = pt.total - - // Update timestamp - cur := time.Now() - pt.last = &cur - } - - pt.Handler(progressInt, speedInt) -} - -type ProgressReader struct { - io.ReadCloser - Tracker *ProgressTracker -} - -func (pt *ProgressReader) Read(p []byte) (int, error) { - // Do normal reader tasks - n, err := pt.ReadCloser.Read(p) - - // Do the actual progress tracking - if pt.Tracker != nil { - pt.Tracker.total += int64(n) - pt.Tracker.Update(n) - } - - return n, err -} - -type ProgressWriter struct { - io.WriteCloser - Tracker *ProgressTracker -} - -func (pt *ProgressWriter) Write(p []byte) (int, error) { - // Do normal writer tasks - n, err := pt.WriteCloser.Write(p) - - // Do the actual progress tracking - if pt.Tracker != nil { - pt.Tracker.total += int64(n) - pt.Tracker.Update(n) - } - - return n, err -} - func RunCommand(name string, arg ...string) error { output, err := exec.Command(name, arg...).CombinedOutput() if err != nil { From 9ad2a56d0f8697dabb9859a391d5f01c85364025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 15 Dec 2016 17:19:29 -0500 Subject: [PATCH 3/5] shared: Give simplestreams client its own package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- client.go | 5 +- lxd/daemon_images.go | 9 +- shared/simplestreams.go | 666 --------------------------------- shared/simplestreams/simplestreams.go | 667 ++++++++++++++++++++++++++++++++++ 4 files changed, 675 insertions(+), 672 deletions(-) delete mode 100644 shared/simplestreams.go create mode 100644 shared/simplestreams/simplestreams.go diff --git a/client.go b/client.go index f7eb6af..0ce204e 100644 --- a/client.go +++ b/client.go @@ -25,6 +25,7 @@ import ( "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/ioprogress" + "github.com/lxc/lxd/shared/simplestreams" ) // Client can talk to a LXD daemon. @@ -39,7 +40,7 @@ type Client struct { Http http.Client websocketDialer websocket.Dialer - simplestreams *shared.SimpleStreams + simplestreams *simplestreams.SimpleStreams } type ResponseType string @@ -338,7 +339,7 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) { } if info.RemoteConfig.Protocol == "simplestreams" { - ss, err := shared.SimpleStreamsClient(c.Remote.Addr, shared.ProxyFromEnvironment) + ss, err := simplestreams.SimpleStreamsClient(c.Remote.Addr, shared.ProxyFromEnvironment) if err != nil { return nil, err } diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go index 6ce6799..5928233 100644 --- a/lxd/daemon_images.go +++ b/lxd/daemon_images.go @@ -17,6 +17,7 @@ import ( "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/ioprogress" + "github.com/lxc/lxd/shared/simplestreams" log "gopkg.in/inconshreveable/log15.v2" ) @@ -26,7 +27,7 @@ type imageStreamCacheEntry struct { Aliases shared.ImageAliases `yaml:"aliases"` Fingerprints []string `yaml:"fingerprints"` expiry time.Time - ss *shared.SimpleStreams + ss *simplestreams.SimpleStreams } var imageStreamCache = map[string]*imageStreamCacheEntry{} @@ -66,7 +67,7 @@ func imageLoadStreamCache(d *Daemon) error { for url, entry := range imageStreamCache { if entry.ss == nil { - ss, err := shared.SimpleStreamsClient(url, d.proxy) + ss, err := simplestreams.SimpleStreamsClient(url, d.proxy) if err != nil { return err } @@ -82,7 +83,7 @@ func imageLoadStreamCache(d *Daemon) error { // downloads the image from a remote server. func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) { var err error - var ss *shared.SimpleStreams + var ss *simplestreams.SimpleStreams var ctxMap log.Ctx if protocol == "" { @@ -98,7 +99,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce if entry == nil || entry.expiry.Before(time.Now()) { refresh := func() (*imageStreamCacheEntry, error) { // Setup simplestreams client - ss, err = shared.SimpleStreamsClient(server, d.proxy) + ss, err = simplestreams.SimpleStreamsClient(server, d.proxy) if err != nil { return nil, err } diff --git a/shared/simplestreams.go b/shared/simplestreams.go deleted file mode 100644 index 8f1ccfa..0000000 --- a/shared/simplestreams.go +++ /dev/null @@ -1,666 +0,0 @@ -package shared - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/lxc/lxd/shared/ioprogress" -) - -type ssSortImage []ImageInfo - -func (a ssSortImage) Len() int { - return len(a) -} - -func (a ssSortImage) Swap(i, j int) { - a[i], a[j] = a[j], a[i] -} - -func (a ssSortImage) Less(i, j int) bool { - if a[i].Properties["os"] == a[j].Properties["os"] { - if a[i].Properties["release"] == a[j].Properties["release"] { - if a[i].CreationDate.UTC().Unix() == 0 { - return true - } - - if a[j].CreationDate.UTC().Unix() == 0 { - return false - } - - return a[i].CreationDate.UTC().Unix() > a[j].CreationDate.UTC().Unix() - } - - if a[i].Properties["release"] == "" { - return false - } - - if a[j].Properties["release"] == "" { - return true - } - - return a[i].Properties["release"] < a[j].Properties["release"] - } - - if a[i].Properties["os"] == "" { - return false - } - - if a[j].Properties["os"] == "" { - return true - } - - return a[i].Properties["os"] < a[j].Properties["os"] -} - -var ssDefaultOS = map[string]string{ - "https://cloud-images.ubuntu.com": "ubuntu", -} - -type SimpleStreamsManifest struct { - Updated string `json:"updated"` - DataType string `json:"datatype"` - Format string `json:"format"` - License string `json:"license"` - Products map[string]SimpleStreamsManifestProduct `json:"products"` -} - -func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) { - downloads := map[string][][]string{} - - images := []ImageInfo{} - nameLayout := "20060102" - eolLayout := "2006-01-02" - - for _, product := range s.Products { - // Skip unsupported architectures - architecture, err := ArchitectureId(product.Architecture) - if err != nil { - continue - } - - architectureName, err := ArchitectureName(architecture) - if err != nil { - continue - } - - for name, version := range product.Versions { - // Short of anything better, use the name as date (see format above) - if len(name) < 8 { - continue - } - - creationDate, err := time.Parse(nameLayout, name[0:8]) - if err != nil { - continue - } - - size := int64(0) - filename := "" - fingerprint := "" - - metaPath := "" - metaHash := "" - rootfsPath := "" - rootfsHash := "" - - found := 0 - for _, item := range version.Items { - // Skip the files we don't care about - if !StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) { - continue - } - found += 1 - - if fingerprint == "" { - if item.LXDHashSha256SquashFs != "" { - fingerprint = item.LXDHashSha256SquashFs - } else if item.LXDHashSha256RootXz != "" { - fingerprint = item.LXDHashSha256RootXz - } else if item.LXDHashSha256 != "" { - fingerprint = item.LXDHashSha256 - } - } - - if item.FileType == "lxd.tar.xz" { - fields := strings.Split(item.Path, "/") - filename = fields[len(fields)-1] - metaPath = item.Path - metaHash = item.HashSha256 - - size += item.Size - } - - if rootfsPath == "" || rootfsHash == "" { - if item.FileType == "squashfs" { - rootfsPath = item.Path - rootfsHash = item.HashSha256 - } - - if item.FileType == "root.tar.xz" { - rootfsPath = item.Path - rootfsHash = item.HashSha256 - } - - size += item.Size - } - } - - if found < 2 || size == 0 || filename == "" || fingerprint == "" { - // Invalid image - continue - } - - // Generate the actual image entry - description := fmt.Sprintf("%s %s %s", product.OperatingSystem, product.ReleaseTitle, product.Architecture) - if version.Label != "" { - description = fmt.Sprintf("%s (%s)", description, version.Label) - } - description = fmt.Sprintf("%s (%s)", description, name) - - image := ImageInfo{} - image.Architecture = architectureName - image.Public = true - image.Size = size - image.CreationDate = creationDate - image.UploadDate = creationDate - image.Filename = filename - image.Fingerprint = fingerprint - image.Properties = map[string]string{ - "os": product.OperatingSystem, - "release": product.Release, - "version": product.Version, - "architecture": product.Architecture, - "label": version.Label, - "serial": name, - "description": description, - } - - // Add the provided aliases - if product.Aliases != "" { - image.Aliases = []ImageAlias{} - for _, entry := range strings.Split(product.Aliases, ",") { - image.Aliases = append(image.Aliases, ImageAlias{Name: entry}) - } - } - - // Clear unset properties - for k, v := range image.Properties { - if v == "" { - delete(image.Properties, k) - } - } - - // Attempt to parse the EOL - image.ExpiryDate = time.Unix(0, 0).UTC() - if product.SupportedEOL != "" { - eolDate, err := time.Parse(eolLayout, product.SupportedEOL) - if err == nil { - image.ExpiryDate = eolDate - } - } - - downloads[fingerprint] = [][]string{[]string{metaPath, metaHash, "meta"}, []string{rootfsPath, rootfsHash, "root"}} - images = append(images, image) - } - } - - return images, downloads -} - -type SimpleStreamsManifestProduct struct { - Aliases string `json:"aliases"` - Architecture string `json:"arch"` - OperatingSystem string `json:"os"` - Release string `json:"release"` - ReleaseCodename string `json:"release_codename"` - ReleaseTitle string `json:"release_title"` - Supported bool `json:"supported"` - SupportedEOL string `json:"support_eol"` - Version string `json:"version"` - Versions map[string]SimpleStreamsManifestProductVersion `json:"versions"` -} - -type SimpleStreamsManifestProductVersion struct { - PublicName string `json:"pubname"` - Label string `json:"label"` - Items map[string]SimpleStreamsManifestProductVersionItem `json:"items"` -} - -type SimpleStreamsManifestProductVersionItem struct { - Path string `json:"path"` - FileType string `json:"ftype"` - HashMd5 string `json:"md5"` - HashSha256 string `json:"sha256"` - LXDHashSha256 string `json:"combined_sha256"` - LXDHashSha256RootXz string `json:"combined_rootxz_sha256"` - LXDHashSha256SquashFs string `json:"combined_squashfs_sha256"` - Size int64 `json:"size"` -} - -type SimpleStreamsIndex struct { - Format string `json:"format"` - Index map[string]SimpleStreamsIndexStream `json:"index"` - Updated string `json:"updated"` -} - -type SimpleStreamsIndexStream struct { - Updated string `json:"updated"` - DataType string `json:"datatype"` - Path string `json:"path"` - Products []string `json:"products"` -} - -func SimpleStreamsClient(url string, proxy func(*http.Request) (*url.URL, error)) (*SimpleStreams, error) { - // Setup a http client - tlsConfig, err := GetTLSConfig("", "", "", nil) - if err != nil { - return nil, err - } - - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - Dial: RFC3493Dialer, - Proxy: proxy, - } - - myHttp := http.Client{ - Transport: tr, - } - - return &SimpleStreams{ - http: &myHttp, - url: url, - cachedManifest: map[string]*SimpleStreamsManifest{}}, nil -} - -type SimpleStreams struct { - http *http.Client - url string - - cachedIndex *SimpleStreamsIndex - cachedManifest map[string]*SimpleStreamsManifest - cachedImages []ImageInfo - cachedAliases map[string]*ImageAliasesEntry -} - -func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) { - if s.cachedIndex != nil { - return s.cachedIndex, nil - } - - req, err := http.NewRequest("GET", fmt.Sprintf("%s/streams/v1/index.json", s.url), nil) - if err != nil { - return nil, err - } - req.Header.Set("User-Agent", UserAgent) - - r, err := s.http.Do(req) - if err != nil { - return nil, err - } - defer r.Body.Close() - - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return nil, err - } - - // Parse the idnex - ssIndex := SimpleStreamsIndex{} - err = json.Unmarshal(body, &ssIndex) - if err != nil { - return nil, err - } - - s.cachedIndex = &ssIndex - - return &ssIndex, nil -} - -func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, error) { - if s.cachedManifest[path] != nil { - return s.cachedManifest[path], nil - } - - req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", s.url, path), nil) - if err != nil { - return nil, err - } - req.Header.Set("User-Agent", UserAgent) - - r, err := s.http.Do(req) - if err != nil { - return nil, err - } - defer r.Body.Close() - - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return nil, err - } - - // Parse the idnex - ssManifest := SimpleStreamsManifest{} - err = json.Unmarshal(body, &ssManifest) - if err != nil { - return nil, err - } - - s.cachedManifest[path] = &ssManifest - - return &ssManifest, nil -} - -func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[string]*ImageAliasesEntry, error) { - aliases := map[string]*ImageAliasesEntry{} - - sort.Sort(ssSortImage(images)) - - defaultOS := "" - for k, v := range ssDefaultOS { - if strings.HasPrefix(s.url, k) { - defaultOS = v - break - } - } - - addAlias := func(name string, fingerprint string) *ImageAlias { - if defaultOS != "" { - name = strings.TrimPrefix(name, fmt.Sprintf("%s/", defaultOS)) - } - - if aliases[name] != nil { - return nil - } - - alias := ImageAliasesEntry{} - alias.Name = name - alias.Target = fingerprint - aliases[name] = &alias - - return &ImageAlias{Name: name} - } - - architectureName, _ := ArchitectureGetLocal() - - newImages := []ImageInfo{} - for _, image := range images { - if image.Aliases != nil { - // Build a new list of aliases from the provided ones - aliases := image.Aliases - image.Aliases = nil - - for _, entry := range aliases { - // Short - if image.Architecture == architectureName { - alias := addAlias(fmt.Sprintf("%s", entry.Name), image.Fingerprint) - if alias != nil { - image.Aliases = append(image.Aliases, *alias) - } - } - - // Medium - alias := addAlias(fmt.Sprintf("%s/%s", entry.Name, image.Properties["architecture"]), image.Fingerprint) - if alias != nil { - image.Aliases = append(image.Aliases, *alias) - } - } - } - - newImages = append(newImages, image) - } - - return newImages, aliases, nil -} - -func (s *SimpleStreams) getImages() ([]ImageInfo, map[string]*ImageAliasesEntry, error) { - if s.cachedImages != nil && s.cachedAliases != nil { - return s.cachedImages, s.cachedAliases, nil - } - - images := []ImageInfo{} - - // Load the main index - ssIndex, err := s.parseIndex() - if err != nil { - return nil, nil, err - } - - // Iterate through the various image manifests - for _, entry := range ssIndex.Index { - // We only care about images - if entry.DataType != "image-downloads" { - continue - } - - // No point downloading an empty image list - if len(entry.Products) == 0 { - continue - } - - manifest, err := s.parseManifest(entry.Path) - if err != nil { - return nil, nil, err - } - - manifestImages, _ := manifest.ToLXD() - - for _, image := range manifestImages { - images = append(images, image) - } - } - - // Setup the aliases - images, aliases, err := s.applyAliases(images) - if err != nil { - return nil, nil, err - } - - s.cachedImages = images - s.cachedAliases = aliases - - return images, aliases, nil -} - -func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) { - // Load the main index - ssIndex, err := s.parseIndex() - if err != nil { - return nil, err - } - - // Iterate through the various image manifests - for _, entry := range ssIndex.Index { - // We only care about images - if entry.DataType != "image-downloads" { - continue - } - - // No point downloading an empty image list - if len(entry.Products) == 0 { - continue - } - - manifest, err := s.parseManifest(entry.Path) - if err != nil { - return nil, err - } - - manifestImages, downloads := manifest.ToLXD() - - for _, image := range manifestImages { - if strings.HasPrefix(image.Fingerprint, fingerprint) { - urls := [][]string{} - for _, path := range downloads[image.Fingerprint] { - urls = append(urls, []string{path[0], path[1], path[2]}) - } - return urls, nil - } - } - } - - return nil, fmt.Errorf("Couldn't find the requested image") -} - -func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int64, int64)) error { - download := func(url string, hash string, target string) error { - out, err := os.Create(target) - if err != nil { - return err - } - defer out.Close() - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return err - } - req.Header.Set("User-Agent", UserAgent) - - resp, err := s.http.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path) - } - - body := &ioprogress.ProgressReader{ - ReadCloser: resp.Body, - Tracker: &ioprogress.ProgressTracker{ - Length: resp.ContentLength, - Handler: progress, - }, - } - - sha256 := sha256.New() - _, err = io.Copy(io.MultiWriter(out, sha256), body) - if err != nil { - return err - } - - result := fmt.Sprintf("%x", sha256.Sum(nil)) - if result != hash { - os.Remove(target) - return fmt.Errorf("Hash mismatch for %s: %s != %s", path, result, hash) - } - - return nil - } - - // Try http first - if strings.HasPrefix(s.url, "https://") { - err := download(fmt.Sprintf("http://%s/%s", strings.TrimPrefix(s.url, "https://"), path), hash, target) - if err == nil { - return nil - } - } - - err := download(fmt.Sprintf("%s/%s", s.url, path), hash, target) - if err != nil { - return err - } - - return nil -} - -func (s *SimpleStreams) ListAliases() (ImageAliases, error) { - _, aliasesMap, err := s.getImages() - if err != nil { - return nil, err - } - - aliases := ImageAliases{} - - for _, alias := range aliasesMap { - aliases = append(aliases, *alias) - } - - return aliases, nil -} - -func (s *SimpleStreams) ListImages() ([]ImageInfo, error) { - images, _, err := s.getImages() - return images, err -} - -func (s *SimpleStreams) GetAlias(name string) string { - _, aliasesMap, err := s.getImages() - if err != nil { - return "" - } - - alias, ok := aliasesMap[name] - if !ok { - return "" - } - - return alias.Target -} - -func (s *SimpleStreams) GetImageInfo(fingerprint string) (*ImageInfo, error) { - images, _, err := s.getImages() - if err != nil { - return nil, err - } - - for _, image := range images { - if strings.HasPrefix(image.Fingerprint, fingerprint) { - return &image, nil - } - } - - return nil, fmt.Errorf("The requested image couldn't be found.") -} - -func (s *SimpleStreams) ExportImage(image string, target string) (string, error) { - if !IsDir(target) { - return "", fmt.Errorf("Split images can only be written to a directory.") - } - - paths, err := s.getPaths(image) - if err != nil { - return "", err - } - - for _, path := range paths { - fields := strings.Split(path[0], "/") - targetFile := filepath.Join(target, fields[len(fields)-1]) - - err := s.downloadFile(path[0], path[1], targetFile, nil) - if err != nil { - return "", err - } - } - - return target, nil -} - -func (s *SimpleStreams) Download(image string, file string, target string, progress func(int64, int64)) error { - paths, err := s.getPaths(image) - if err != nil { - return err - } - - for _, path := range paths { - if file != path[2] { - continue - } - - return s.downloadFile(path[0], path[1], target, progress) - } - - return fmt.Errorf("The file couldn't be found.") -} diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go new file mode 100644 index 0000000..b0f9ee3 --- /dev/null +++ b/shared/simplestreams/simplestreams.go @@ -0,0 +1,667 @@ +package simplestreams + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/ioprogress" +) + +type ssSortImage []shared.ImageInfo + +func (a ssSortImage) Len() int { + return len(a) +} + +func (a ssSortImage) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a ssSortImage) Less(i, j int) bool { + if a[i].Properties["os"] == a[j].Properties["os"] { + if a[i].Properties["release"] == a[j].Properties["release"] { + if a[i].CreationDate.UTC().Unix() == 0 { + return true + } + + if a[j].CreationDate.UTC().Unix() == 0 { + return false + } + + return a[i].CreationDate.UTC().Unix() > a[j].CreationDate.UTC().Unix() + } + + if a[i].Properties["release"] == "" { + return false + } + + if a[j].Properties["release"] == "" { + return true + } + + return a[i].Properties["release"] < a[j].Properties["release"] + } + + if a[i].Properties["os"] == "" { + return false + } + + if a[j].Properties["os"] == "" { + return true + } + + return a[i].Properties["os"] < a[j].Properties["os"] +} + +var ssDefaultOS = map[string]string{ + "https://cloud-images.ubuntu.com": "ubuntu", +} + +type SimpleStreamsManifest struct { + Updated string `json:"updated"` + DataType string `json:"datatype"` + Format string `json:"format"` + License string `json:"license"` + Products map[string]SimpleStreamsManifestProduct `json:"products"` +} + +func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]string) { + downloads := map[string][][]string{} + + images := []shared.ImageInfo{} + nameLayout := "20060102" + eolLayout := "2006-01-02" + + for _, product := range s.Products { + // Skip unsupported architectures + architecture, err := shared.ArchitectureId(product.Architecture) + if err != nil { + continue + } + + architectureName, err := shared.ArchitectureName(architecture) + if err != nil { + continue + } + + for name, version := range product.Versions { + // Short of anything better, use the name as date (see format above) + if len(name) < 8 { + continue + } + + creationDate, err := time.Parse(nameLayout, name[0:8]) + if err != nil { + continue + } + + size := int64(0) + filename := "" + fingerprint := "" + + metaPath := "" + metaHash := "" + rootfsPath := "" + rootfsHash := "" + + found := 0 + for _, item := range version.Items { + // Skip the files we don't care about + if !shared.StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) { + continue + } + found += 1 + + if fingerprint == "" { + if item.LXDHashSha256SquashFs != "" { + fingerprint = item.LXDHashSha256SquashFs + } else if item.LXDHashSha256RootXz != "" { + fingerprint = item.LXDHashSha256RootXz + } else if item.LXDHashSha256 != "" { + fingerprint = item.LXDHashSha256 + } + } + + if item.FileType == "lxd.tar.xz" { + fields := strings.Split(item.Path, "/") + filename = fields[len(fields)-1] + metaPath = item.Path + metaHash = item.HashSha256 + + size += item.Size + } + + if rootfsPath == "" || rootfsHash == "" { + if item.FileType == "squashfs" { + rootfsPath = item.Path + rootfsHash = item.HashSha256 + } + + if item.FileType == "root.tar.xz" { + rootfsPath = item.Path + rootfsHash = item.HashSha256 + } + + size += item.Size + } + } + + if found < 2 || size == 0 || filename == "" || fingerprint == "" { + // Invalid image + continue + } + + // Generate the actual image entry + description := fmt.Sprintf("%s %s %s", product.OperatingSystem, product.ReleaseTitle, product.Architecture) + if version.Label != "" { + description = fmt.Sprintf("%s (%s)", description, version.Label) + } + description = fmt.Sprintf("%s (%s)", description, name) + + image := shared.ImageInfo{} + image.Architecture = architectureName + image.Public = true + image.Size = size + image.CreationDate = creationDate + image.UploadDate = creationDate + image.Filename = filename + image.Fingerprint = fingerprint + image.Properties = map[string]string{ + "os": product.OperatingSystem, + "release": product.Release, + "version": product.Version, + "architecture": product.Architecture, + "label": version.Label, + "serial": name, + "description": description, + } + + // Add the provided aliases + if product.Aliases != "" { + image.Aliases = []shared.ImageAlias{} + for _, entry := range strings.Split(product.Aliases, ",") { + image.Aliases = append(image.Aliases, shared.ImageAlias{Name: entry}) + } + } + + // Clear unset properties + for k, v := range image.Properties { + if v == "" { + delete(image.Properties, k) + } + } + + // Attempt to parse the EOL + image.ExpiryDate = time.Unix(0, 0).UTC() + if product.SupportedEOL != "" { + eolDate, err := time.Parse(eolLayout, product.SupportedEOL) + if err == nil { + image.ExpiryDate = eolDate + } + } + + downloads[fingerprint] = [][]string{[]string{metaPath, metaHash, "meta"}, []string{rootfsPath, rootfsHash, "root"}} + images = append(images, image) + } + } + + return images, downloads +} + +type SimpleStreamsManifestProduct struct { + Aliases string `json:"aliases"` + Architecture string `json:"arch"` + OperatingSystem string `json:"os"` + Release string `json:"release"` + ReleaseCodename string `json:"release_codename"` + ReleaseTitle string `json:"release_title"` + Supported bool `json:"supported"` + SupportedEOL string `json:"support_eol"` + Version string `json:"version"` + Versions map[string]SimpleStreamsManifestProductVersion `json:"versions"` +} + +type SimpleStreamsManifestProductVersion struct { + PublicName string `json:"pubname"` + Label string `json:"label"` + Items map[string]SimpleStreamsManifestProductVersionItem `json:"items"` +} + +type SimpleStreamsManifestProductVersionItem struct { + Path string `json:"path"` + FileType string `json:"ftype"` + HashMd5 string `json:"md5"` + HashSha256 string `json:"sha256"` + LXDHashSha256 string `json:"combined_sha256"` + LXDHashSha256RootXz string `json:"combined_rootxz_sha256"` + LXDHashSha256SquashFs string `json:"combined_squashfs_sha256"` + Size int64 `json:"size"` +} + +type SimpleStreamsIndex struct { + Format string `json:"format"` + Index map[string]SimpleStreamsIndexStream `json:"index"` + Updated string `json:"updated"` +} + +type SimpleStreamsIndexStream struct { + Updated string `json:"updated"` + DataType string `json:"datatype"` + Path string `json:"path"` + Products []string `json:"products"` +} + +func SimpleStreamsClient(url string, proxy func(*http.Request) (*url.URL, error)) (*SimpleStreams, error) { + // Setup a http client + tlsConfig, err := shared.GetTLSConfig("", "", "", nil) + if err != nil { + return nil, err + } + + tr := &http.Transport{ + TLSClientConfig: tlsConfig, + Dial: shared.RFC3493Dialer, + Proxy: proxy, + } + + myHttp := http.Client{ + Transport: tr, + } + + return &SimpleStreams{ + http: &myHttp, + url: url, + cachedManifest: map[string]*SimpleStreamsManifest{}}, nil +} + +type SimpleStreams struct { + http *http.Client + url string + + cachedIndex *SimpleStreamsIndex + cachedManifest map[string]*SimpleStreamsManifest + cachedImages []shared.ImageInfo + cachedAliases map[string]*shared.ImageAliasesEntry +} + +func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) { + if s.cachedIndex != nil { + return s.cachedIndex, nil + } + + req, err := http.NewRequest("GET", fmt.Sprintf("%s/streams/v1/index.json", s.url), nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", shared.UserAgent) + + r, err := s.http.Do(req) + if err != nil { + return nil, err + } + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + + // Parse the idnex + ssIndex := SimpleStreamsIndex{} + err = json.Unmarshal(body, &ssIndex) + if err != nil { + return nil, err + } + + s.cachedIndex = &ssIndex + + return &ssIndex, nil +} + +func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, error) { + if s.cachedManifest[path] != nil { + return s.cachedManifest[path], nil + } + + req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", s.url, path), nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", shared.UserAgent) + + r, err := s.http.Do(req) + if err != nil { + return nil, err + } + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + + // Parse the idnex + ssManifest := SimpleStreamsManifest{} + err = json.Unmarshal(body, &ssManifest) + if err != nil { + return nil, err + } + + s.cachedManifest[path] = &ssManifest + + return &ssManifest, nil +} + +func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) { + aliases := map[string]*shared.ImageAliasesEntry{} + + sort.Sort(ssSortImage(images)) + + defaultOS := "" + for k, v := range ssDefaultOS { + if strings.HasPrefix(s.url, k) { + defaultOS = v + break + } + } + + addAlias := func(name string, fingerprint string) *shared.ImageAlias { + if defaultOS != "" { + name = strings.TrimPrefix(name, fmt.Sprintf("%s/", defaultOS)) + } + + if aliases[name] != nil { + return nil + } + + alias := shared.ImageAliasesEntry{} + alias.Name = name + alias.Target = fingerprint + aliases[name] = &alias + + return &shared.ImageAlias{Name: name} + } + + architectureName, _ := shared.ArchitectureGetLocal() + + newImages := []shared.ImageInfo{} + for _, image := range images { + if image.Aliases != nil { + // Build a new list of aliases from the provided ones + aliases := image.Aliases + image.Aliases = nil + + for _, entry := range aliases { + // Short + if image.Architecture == architectureName { + alias := addAlias(fmt.Sprintf("%s", entry.Name), image.Fingerprint) + if alias != nil { + image.Aliases = append(image.Aliases, *alias) + } + } + + // Medium + alias := addAlias(fmt.Sprintf("%s/%s", entry.Name, image.Properties["architecture"]), image.Fingerprint) + if alias != nil { + image.Aliases = append(image.Aliases, *alias) + } + } + } + + newImages = append(newImages, image) + } + + return newImages, aliases, nil +} + +func (s *SimpleStreams) getImages() ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) { + if s.cachedImages != nil && s.cachedAliases != nil { + return s.cachedImages, s.cachedAliases, nil + } + + images := []shared.ImageInfo{} + + // Load the main index + ssIndex, err := s.parseIndex() + if err != nil { + return nil, nil, err + } + + // Iterate through the various image manifests + for _, entry := range ssIndex.Index { + // We only care about images + if entry.DataType != "image-downloads" { + continue + } + + // No point downloading an empty image list + if len(entry.Products) == 0 { + continue + } + + manifest, err := s.parseManifest(entry.Path) + if err != nil { + return nil, nil, err + } + + manifestImages, _ := manifest.ToLXD() + + for _, image := range manifestImages { + images = append(images, image) + } + } + + // Setup the aliases + images, aliases, err := s.applyAliases(images) + if err != nil { + return nil, nil, err + } + + s.cachedImages = images + s.cachedAliases = aliases + + return images, aliases, nil +} + +func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) { + // Load the main index + ssIndex, err := s.parseIndex() + if err != nil { + return nil, err + } + + // Iterate through the various image manifests + for _, entry := range ssIndex.Index { + // We only care about images + if entry.DataType != "image-downloads" { + continue + } + + // No point downloading an empty image list + if len(entry.Products) == 0 { + continue + } + + manifest, err := s.parseManifest(entry.Path) + if err != nil { + return nil, err + } + + manifestImages, downloads := manifest.ToLXD() + + for _, image := range manifestImages { + if strings.HasPrefix(image.Fingerprint, fingerprint) { + urls := [][]string{} + for _, path := range downloads[image.Fingerprint] { + urls = append(urls, []string{path[0], path[1], path[2]}) + } + return urls, nil + } + } + } + + return nil, fmt.Errorf("Couldn't find the requested image") +} + +func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int64, int64)) error { + download := func(url string, hash string, target string) error { + out, err := os.Create(target) + if err != nil { + return err + } + defer out.Close() + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + req.Header.Set("User-Agent", shared.UserAgent) + + resp, err := s.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path) + } + + body := &ioprogress.ProgressReader{ + ReadCloser: resp.Body, + Tracker: &ioprogress.ProgressTracker{ + Length: resp.ContentLength, + Handler: progress, + }, + } + + sha256 := sha256.New() + _, err = io.Copy(io.MultiWriter(out, sha256), body) + if err != nil { + return err + } + + result := fmt.Sprintf("%x", sha256.Sum(nil)) + if result != hash { + os.Remove(target) + return fmt.Errorf("Hash mismatch for %s: %s != %s", path, result, hash) + } + + return nil + } + + // Try http first + if strings.HasPrefix(s.url, "https://") { + err := download(fmt.Sprintf("http://%s/%s", strings.TrimPrefix(s.url, "https://"), path), hash, target) + if err == nil { + return nil + } + } + + err := download(fmt.Sprintf("%s/%s", s.url, path), hash, target) + if err != nil { + return err + } + + return nil +} + +func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) { + _, aliasesMap, err := s.getImages() + if err != nil { + return nil, err + } + + aliases := shared.ImageAliases{} + + for _, alias := range aliasesMap { + aliases = append(aliases, *alias) + } + + return aliases, nil +} + +func (s *SimpleStreams) ListImages() ([]shared.ImageInfo, error) { + images, _, err := s.getImages() + return images, err +} + +func (s *SimpleStreams) GetAlias(name string) string { + _, aliasesMap, err := s.getImages() + if err != nil { + return "" + } + + alias, ok := aliasesMap[name] + if !ok { + return "" + } + + return alias.Target +} + +func (s *SimpleStreams) GetImageInfo(fingerprint string) (*shared.ImageInfo, error) { + images, _, err := s.getImages() + if err != nil { + return nil, err + } + + for _, image := range images { + if strings.HasPrefix(image.Fingerprint, fingerprint) { + return &image, nil + } + } + + return nil, fmt.Errorf("The requested image couldn't be found.") +} + +func (s *SimpleStreams) ExportImage(image string, target string) (string, error) { + if !shared.IsDir(target) { + return "", fmt.Errorf("Split images can only be written to a directory.") + } + + paths, err := s.getPaths(image) + if err != nil { + return "", err + } + + for _, path := range paths { + fields := strings.Split(path[0], "/") + targetFile := filepath.Join(target, fields[len(fields)-1]) + + err := s.downloadFile(path[0], path[1], targetFile, nil) + if err != nil { + return "", err + } + } + + return target, nil +} + +func (s *SimpleStreams) Download(image string, file string, target string, progress func(int64, int64)) error { + paths, err := s.getPaths(image) + if err != nil { + return err + } + + for _, path := range paths { + if file != path[2] { + continue + } + + return s.downloadFile(path[0], path[1], target, progress) + } + + return fmt.Errorf("The file couldn't be found.") +} From a57aac3c27ef1c9d07939d5ebfd781790870d138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 15 Dec 2016 17:54:53 -0500 Subject: [PATCH 4/5] shared: Give Architecture handling its own package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/api_1.0.go | 3 +- lxd/api_internal.go | 5 +- lxd/container.go | 9 +-- lxd/container_lxc.go | 19 +++--- lxd/container_patch.go | 4 +- lxd/container_put.go | 4 +- lxd/containers_post.go | 8 ++- lxd/daemon.go | 7 ++- lxd/db_images.go | 7 ++- lxd/images.go | 5 +- lxd/seccomp.go | 3 +- shared/architectures.go | 107 ---------------------------------- shared/architectures_linux.go | 24 -------- shared/architectures_others.go | 7 --- shared/osarch/architectures.go | 107 ++++++++++++++++++++++++++++++++++ shared/osarch/architectures_linux.go | 24 ++++++++ shared/osarch/architectures_others.go | 7 +++ shared/simplestreams/simplestreams.go | 7 ++- 18 files changed, 186 insertions(+), 171 deletions(-) delete mode 100644 shared/architectures.go delete mode 100644 shared/architectures_linux.go delete mode 100644 shared/architectures_others.go create mode 100644 shared/osarch/architectures.go create mode 100644 shared/osarch/architectures_linux.go create mode 100644 shared/osarch/architectures_others.go diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go index 8ffd973..3117106 100644 --- a/lxd/api_1.0.go +++ b/lxd/api_1.0.go @@ -11,6 +11,7 @@ import ( "gopkg.in/lxc/go-lxc.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" ) var api10 = []Command{ @@ -139,7 +140,7 @@ func api10Get(d *Daemon, r *http.Request) Response { architectures := []string{} for _, architecture := range d.architectures { - architectureName, err := shared.ArchitectureName(architecture) + architectureName, err := osarch.ArchitectureName(architecture) if err != nil { return InternalError(err) } diff --git a/lxd/api_internal.go b/lxd/api_internal.go index 6723535..7fa2205 100644 --- a/lxd/api_internal.go +++ b/lxd/api_internal.go @@ -11,6 +11,7 @@ import ( "gopkg.in/yaml.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) @@ -141,7 +142,7 @@ func internalImport(d *Daemon, r *http.Request) Response { } } - arch, err := shared.ArchitectureId(sf.Container.Architecture) + arch, err := osarch.ArchitectureId(sf.Container.Architecture) if err != nil { return SmartError(err) } @@ -170,7 +171,7 @@ func internalImport(d *Daemon, r *http.Request) Response { } } - arch, err := shared.ArchitectureId(snap.Architecture) + arch, err := osarch.ArchitectureId(snap.Architecture) if err != nil { return SmartError(err) } diff --git a/lxd/container.go b/lxd/container.go index fdd3223..6b43e07 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -10,6 +10,7 @@ import ( "gopkg.in/lxc/go-lxc.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" ) // Helper functions @@ -48,9 +49,9 @@ func containerValidConfigKey(d *Daemon, key string, value string) error { } if key == "security.syscalls.blacklist_compat" { for _, arch := range d.architectures { - if arch == shared.ARCH_64BIT_INTEL_X86 || - arch == shared.ARCH_64BIT_ARMV8_LITTLE_ENDIAN || - arch == shared.ARCH_64BIT_POWERPC_BIG_ENDIAN { + if arch == osarch.ARCH_64BIT_INTEL_X86 || + arch == osarch.ARCH_64BIT_ARMV8_LITTLE_ENDIAN || + arch == osarch.ARCH_64BIT_POWERPC_BIG_ENDIAN { return nil } } @@ -623,7 +624,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) { } // Validate architecture - _, err = shared.ArchitectureName(args.Architecture) + _, err = osarch.ArchitectureName(args.Architecture) if err != nil { return nil, err } diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index a24b5ce..95232bc 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -25,6 +25,7 @@ import ( "gopkg.in/yaml.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) @@ -870,9 +871,9 @@ func (c *containerLXC) initLXC() error { } // Setup architecture - personality, err := shared.ArchitecturePersonality(c.architecture) + personality, err := osarch.ArchitecturePersonality(c.architecture) if err != nil { - personality, err = shared.ArchitecturePersonality(c.daemon.architectures[0]) + personality, err = osarch.ArchitecturePersonality(c.daemon.architectures[0]) if err != nil { return err } @@ -2380,7 +2381,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) { } // Ignore err as the arch string on error is correct (unknown) - architectureName, _ := shared.ArchitectureName(c.architecture) + architectureName, _ := osarch.ArchitectureName(c.architecture) // Prepare the ETag etag := []interface{}{c.architecture, c.localConfig, c.localDevices, c.ephemeral, c.profiles} @@ -2882,7 +2883,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error { // Validate the new architecture if args.Architecture != 0 { - _, err = shared.ArchitectureName(args.Architecture) + _, err = osarch.ArchitectureName(args.Architecture) if err != nil { return fmt.Errorf("Invalid architecture id: %s", err) } @@ -3694,13 +3695,13 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error { return err } - arch, _ = shared.ArchitectureName(parent.Architecture()) + arch, _ = osarch.ArchitectureName(parent.Architecture()) } else { - arch, _ = shared.ArchitectureName(c.architecture) + arch, _ = osarch.ArchitectureName(c.architecture) } if arch == "" { - arch, err = shared.ArchitectureName(c.daemon.architectures[0]) + arch, err = osarch.ArchitectureName(c.daemon.architectures[0]) if err != nil { shared.LogError("Failed exporting container", ctxMap) return err @@ -4118,9 +4119,9 @@ func (c *containerLXC) templateApplyNow(trigger string) error { } // Figure out the architecture - arch, err := shared.ArchitectureName(c.architecture) + arch, err := osarch.ArchitectureName(c.architecture) if err != nil { - arch, err = shared.ArchitectureName(c.daemon.architectures[0]) + arch, err = osarch.ArchitectureName(c.daemon.architectures[0]) if err != nil { return err } diff --git a/lxd/container_patch.go b/lxd/container_patch.go index 5bf7c79..f7752bb 100644 --- a/lxd/container_patch.go +++ b/lxd/container_patch.go @@ -8,7 +8,9 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" ) func containerPatch(d *Daemon, r *http.Request) Response { @@ -54,7 +56,7 @@ func containerPatch(d *Daemon, r *http.Request) Response { if err != nil { architecture = c.Architecture() } else { - architecture, err = shared.ArchitectureId(req.Architecture) + architecture, err = osarch.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } diff --git a/lxd/container_put.go b/lxd/container_put.go index b6aca32..471b022 100644 --- a/lxd/container_put.go +++ b/lxd/container_put.go @@ -7,7 +7,9 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) @@ -45,7 +47,7 @@ func containerPut(d *Daemon, r *http.Request) Response { return BadRequest(err) } - architecture, err := shared.ArchitectureId(configRaw.Architecture) + architecture, err := osarch.ArchitectureId(configRaw.Architecture) if err != nil { architecture = 0 } diff --git a/lxd/containers_post.go b/lxd/containers_post.go index 21e6742..93b21a4 100644 --- a/lxd/containers_post.go +++ b/lxd/containers_post.go @@ -10,7 +10,9 @@ import ( "github.com/dustinkirkland/golang-petname" "github.com/gorilla/websocket" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) @@ -139,7 +141,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response { hash = imgInfo.Fingerprint - architecture, err := shared.ArchitectureId(imgInfo.Architecture) + architecture, err := osarch.ArchitectureId(imgInfo.Architecture) if err != nil { architecture = 0 } @@ -171,7 +173,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response { } func createFromNone(d *Daemon, req *containerPostReq) Response { - architecture, err := shared.ArchitectureId(req.Architecture) + architecture, err := osarch.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } @@ -207,7 +209,7 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response { return NotImplemented } - architecture, err := shared.ArchitectureId(req.Architecture) + architecture, err := osarch.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } diff --git a/lxd/daemon.go b/lxd/daemon.go index 4687c8c..d2dcd31 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -32,6 +32,7 @@ import ( "github.com/lxc/lxd" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/logging" + "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) @@ -712,18 +713,18 @@ func (d *Daemon) Init() error { /* Get the list of supported architectures */ var architectures = []int{} - architectureName, err := shared.ArchitectureGetLocal() + architectureName, err := osarch.ArchitectureGetLocal() if err != nil { return err } - architecture, err := shared.ArchitectureId(architectureName) + architecture, err := osarch.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) - personalities, err := shared.ArchitecturePersonalities(architecture) + personalities, err := osarch.ArchitecturePersonalities(architecture) if err != nil { return err } diff --git a/lxd/db_images.go b/lxd/db_images.go index 76ce7bb..ac515a3 100644 --- a/lxd/db_images.go +++ b/lxd/db_images.go @@ -8,6 +8,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" ) var dbImageSourceProtocol = map[int]string{ @@ -175,7 +176,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool image.LastUsedDate = time.Time{} } - image.Architecture, _ = shared.ArchitectureName(arch) + image.Architecture, _ = osarch.ArchitectureName(arch) // The upload date is enforced by NOT NULL in the schema, so it can never be nil. image.UploadDate = *upload @@ -312,7 +313,7 @@ func dbImageLastAccessInit(db *sql.DB, fingerprint string) error { } func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error { - arch, err := shared.ArchitectureId(architecture) + arch, err := osarch.ArchitectureId(architecture) if err != nil { arch = 0 } @@ -369,7 +370,7 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto } func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error { - arch, err := shared.ArchitectureId(architecture) + arch, err := osarch.ArchitectureId(architecture) if err != nil { arch = 0 } diff --git a/lxd/images.go b/lxd/images.go index 291789f..c6724e1 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -24,6 +24,7 @@ import ( "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/logging" + "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) @@ -319,7 +320,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq, return info, err } - info.Architecture, _ = shared.ArchitectureName(c.Architecture()) + info.Architecture, _ = osarch.ArchitectureName(c.Architecture()) info.Properties = req.Properties return info, nil @@ -804,7 +805,7 @@ func getImageMetadata(fname string) (*imageMetadata, error) { return nil, fmt.Errorf("Could not parse %s: %v", metadataName, err) } - _, err = shared.ArchitectureId(metadata.Architecture) + _, err = osarch.ArchitectureId(metadata.Architecture) if err != nil { return nil, err } diff --git a/lxd/seccomp.go b/lxd/seccomp.go index 7e40330..1c9bb4c 100644 --- a/lxd/seccomp.go +++ b/lxd/seccomp.go @@ -7,6 +7,7 @@ import ( "path" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/osarch" ) const SECCOMP_HEADER = `2 @@ -121,7 +122,7 @@ func getSeccompProfileContent(c container) (string, error) { compat := config["security.syscalls.blacklist_compat"] if shared.IsTrue(compat) { - arch, err := shared.ArchitectureName(c.Architecture()) + arch, err := osarch.ArchitectureName(c.Architecture()) if err != nil { return "", err } diff --git a/shared/architectures.go b/shared/architectures.go deleted file mode 100644 index 0f4f9ba..0000000 --- a/shared/architectures.go +++ /dev/null @@ -1,107 +0,0 @@ -package shared - -import ( - "fmt" -) - -const ( - ARCH_UNKNOWN = 0 - ARCH_32BIT_INTEL_X86 = 1 - ARCH_64BIT_INTEL_X86 = 2 - ARCH_32BIT_ARMV7_LITTLE_ENDIAN = 3 - ARCH_64BIT_ARMV8_LITTLE_ENDIAN = 4 - ARCH_32BIT_POWERPC_BIG_ENDIAN = 5 - ARCH_64BIT_POWERPC_BIG_ENDIAN = 6 - ARCH_64BIT_POWERPC_LITTLE_ENDIAN = 7 - ARCH_64BIT_S390_BIG_ENDIAN = 8 -) - -var architectureNames = map[int]string{ - ARCH_32BIT_INTEL_X86: "i686", - ARCH_64BIT_INTEL_X86: "x86_64", - ARCH_32BIT_ARMV7_LITTLE_ENDIAN: "armv7l", - ARCH_64BIT_ARMV8_LITTLE_ENDIAN: "aarch64", - ARCH_32BIT_POWERPC_BIG_ENDIAN: "ppc", - ARCH_64BIT_POWERPC_BIG_ENDIAN: "ppc64", - ARCH_64BIT_POWERPC_LITTLE_ENDIAN: "ppc64le", - ARCH_64BIT_S390_BIG_ENDIAN: "s390x", -} - -var architectureAliases = map[int][]string{ - ARCH_32BIT_INTEL_X86: []string{"i386"}, - ARCH_64BIT_INTEL_X86: []string{"amd64"}, - ARCH_32BIT_ARMV7_LITTLE_ENDIAN: []string{"armel", "armhf"}, - ARCH_64BIT_ARMV8_LITTLE_ENDIAN: []string{"arm64"}, - ARCH_32BIT_POWERPC_BIG_ENDIAN: []string{"powerpc"}, - ARCH_64BIT_POWERPC_BIG_ENDIAN: []string{"powerpc64"}, - ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []string{"ppc64el"}, -} - -var architecturePersonalities = map[int]string{ - ARCH_32BIT_INTEL_X86: "linux32", - ARCH_64BIT_INTEL_X86: "linux64", - ARCH_32BIT_ARMV7_LITTLE_ENDIAN: "linux32", - ARCH_64BIT_ARMV8_LITTLE_ENDIAN: "linux64", - ARCH_32BIT_POWERPC_BIG_ENDIAN: "linux32", - ARCH_64BIT_POWERPC_BIG_ENDIAN: "linux64", - ARCH_64BIT_POWERPC_LITTLE_ENDIAN: "linux64", - ARCH_64BIT_S390_BIG_ENDIAN: "linux64", -} - -var architectureSupportedPersonalities = map[int][]int{ - ARCH_32BIT_INTEL_X86: []int{}, - ARCH_64BIT_INTEL_X86: []int{ARCH_32BIT_INTEL_X86}, - ARCH_32BIT_ARMV7_LITTLE_ENDIAN: []int{}, - ARCH_64BIT_ARMV8_LITTLE_ENDIAN: []int{ARCH_32BIT_ARMV7_LITTLE_ENDIAN}, - ARCH_32BIT_POWERPC_BIG_ENDIAN: []int{}, - ARCH_64BIT_POWERPC_BIG_ENDIAN: []int{ARCH_32BIT_POWERPC_BIG_ENDIAN}, - ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []int{}, - ARCH_64BIT_S390_BIG_ENDIAN: []int{}, -} - -const ArchitectureDefault = "x86_64" - -func ArchitectureName(arch int) (string, error) { - arch_name, exists := architectureNames[arch] - if exists { - return arch_name, nil - } - - return "unknown", fmt.Errorf("Architecture isn't supported: %d", arch) -} - -func ArchitectureId(arch string) (int, error) { - for arch_id, arch_name := range architectureNames { - if arch_name == arch { - return arch_id, nil - } - } - - for arch_id, arch_aliases := range architectureAliases { - for _, arch_name := range arch_aliases { - if arch_name == arch { - return arch_id, nil - } - } - } - - return 0, fmt.Errorf("Architecture isn't supported: %s", arch) -} - -func ArchitecturePersonality(arch int) (string, error) { - arch_personality, exists := architecturePersonalities[arch] - if exists { - return arch_personality, nil - } - - return "", fmt.Errorf("Architecture isn't supported: %d", arch) -} - -func ArchitecturePersonalities(arch int) ([]int, error) { - personalities, exists := architectureSupportedPersonalities[arch] - if exists { - return personalities, nil - } - - return []int{}, fmt.Errorf("Architecture isn't supported: %d", arch) -} diff --git a/shared/architectures_linux.go b/shared/architectures_linux.go deleted file mode 100644 index 0cc82ff..0000000 --- a/shared/architectures_linux.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build linux - -package shared - -import ( - "syscall" -) - -func ArchitectureGetLocal() (string, error) { - uname := syscall.Utsname{} - if err := syscall.Uname(&uname); err != nil { - return ArchitectureDefault, err - } - - architectureName := "" - for _, c := range uname.Machine { - if c == 0 { - break - } - architectureName += string(byte(c)) - } - - return architectureName, nil -} diff --git a/shared/architectures_others.go b/shared/architectures_others.go deleted file mode 100644 index 5609316..0000000 --- a/shared/architectures_others.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build !linux - -package shared - -func ArchitectureGetLocal() (string, error) { - return ArchitectureDefault, nil -} diff --git a/shared/osarch/architectures.go b/shared/osarch/architectures.go new file mode 100644 index 0000000..728098c --- /dev/null +++ b/shared/osarch/architectures.go @@ -0,0 +1,107 @@ +package osarch + +import ( + "fmt" +) + +const ( + ARCH_UNKNOWN = 0 + ARCH_32BIT_INTEL_X86 = 1 + ARCH_64BIT_INTEL_X86 = 2 + ARCH_32BIT_ARMV7_LITTLE_ENDIAN = 3 + ARCH_64BIT_ARMV8_LITTLE_ENDIAN = 4 + ARCH_32BIT_POWERPC_BIG_ENDIAN = 5 + ARCH_64BIT_POWERPC_BIG_ENDIAN = 6 + ARCH_64BIT_POWERPC_LITTLE_ENDIAN = 7 + ARCH_64BIT_S390_BIG_ENDIAN = 8 +) + +var architectureNames = map[int]string{ + ARCH_32BIT_INTEL_X86: "i686", + ARCH_64BIT_INTEL_X86: "x86_64", + ARCH_32BIT_ARMV7_LITTLE_ENDIAN: "armv7l", + ARCH_64BIT_ARMV8_LITTLE_ENDIAN: "aarch64", + ARCH_32BIT_POWERPC_BIG_ENDIAN: "ppc", + ARCH_64BIT_POWERPC_BIG_ENDIAN: "ppc64", + ARCH_64BIT_POWERPC_LITTLE_ENDIAN: "ppc64le", + ARCH_64BIT_S390_BIG_ENDIAN: "s390x", +} + +var architectureAliases = map[int][]string{ + ARCH_32BIT_INTEL_X86: []string{"i386"}, + ARCH_64BIT_INTEL_X86: []string{"amd64"}, + ARCH_32BIT_ARMV7_LITTLE_ENDIAN: []string{"armel", "armhf"}, + ARCH_64BIT_ARMV8_LITTLE_ENDIAN: []string{"arm64"}, + ARCH_32BIT_POWERPC_BIG_ENDIAN: []string{"powerpc"}, + ARCH_64BIT_POWERPC_BIG_ENDIAN: []string{"powerpc64"}, + ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []string{"ppc64el"}, +} + +var architecturePersonalities = map[int]string{ + ARCH_32BIT_INTEL_X86: "linux32", + ARCH_64BIT_INTEL_X86: "linux64", + ARCH_32BIT_ARMV7_LITTLE_ENDIAN: "linux32", + ARCH_64BIT_ARMV8_LITTLE_ENDIAN: "linux64", + ARCH_32BIT_POWERPC_BIG_ENDIAN: "linux32", + ARCH_64BIT_POWERPC_BIG_ENDIAN: "linux64", + ARCH_64BIT_POWERPC_LITTLE_ENDIAN: "linux64", + ARCH_64BIT_S390_BIG_ENDIAN: "linux64", +} + +var architectureSupportedPersonalities = map[int][]int{ + ARCH_32BIT_INTEL_X86: []int{}, + ARCH_64BIT_INTEL_X86: []int{ARCH_32BIT_INTEL_X86}, + ARCH_32BIT_ARMV7_LITTLE_ENDIAN: []int{}, + ARCH_64BIT_ARMV8_LITTLE_ENDIAN: []int{ARCH_32BIT_ARMV7_LITTLE_ENDIAN}, + ARCH_32BIT_POWERPC_BIG_ENDIAN: []int{}, + ARCH_64BIT_POWERPC_BIG_ENDIAN: []int{ARCH_32BIT_POWERPC_BIG_ENDIAN}, + ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []int{}, + ARCH_64BIT_S390_BIG_ENDIAN: []int{}, +} + +const ArchitectureDefault = "x86_64" + +func ArchitectureName(arch int) (string, error) { + arch_name, exists := architectureNames[arch] + if exists { + return arch_name, nil + } + + return "unknown", fmt.Errorf("Architecture isn't supported: %d", arch) +} + +func ArchitectureId(arch string) (int, error) { + for arch_id, arch_name := range architectureNames { + if arch_name == arch { + return arch_id, nil + } + } + + for arch_id, arch_aliases := range architectureAliases { + for _, arch_name := range arch_aliases { + if arch_name == arch { + return arch_id, nil + } + } + } + + return 0, fmt.Errorf("Architecture isn't supported: %s", arch) +} + +func ArchitecturePersonality(arch int) (string, error) { + arch_personality, exists := architecturePersonalities[arch] + if exists { + return arch_personality, nil + } + + return "", fmt.Errorf("Architecture isn't supported: %d", arch) +} + +func ArchitecturePersonalities(arch int) ([]int, error) { + personalities, exists := architectureSupportedPersonalities[arch] + if exists { + return personalities, nil + } + + return []int{}, fmt.Errorf("Architecture isn't supported: %d", arch) +} diff --git a/shared/osarch/architectures_linux.go b/shared/osarch/architectures_linux.go new file mode 100644 index 0000000..c95b58a --- /dev/null +++ b/shared/osarch/architectures_linux.go @@ -0,0 +1,24 @@ +// +build linux + +package osarch + +import ( + "syscall" +) + +func ArchitectureGetLocal() (string, error) { + uname := syscall.Utsname{} + if err := syscall.Uname(&uname); err != nil { + return ArchitectureDefault, err + } + + architectureName := "" + for _, c := range uname.Machine { + if c == 0 { + break + } + architectureName += string(byte(c)) + } + + return architectureName, nil +} diff --git a/shared/osarch/architectures_others.go b/shared/osarch/architectures_others.go new file mode 100644 index 0000000..22bd1ea --- /dev/null +++ b/shared/osarch/architectures_others.go @@ -0,0 +1,7 @@ +// +build !linux + +package osarch + +func ArchitectureGetLocal() (string, error) { + return ArchitectureDefault, nil +} diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go index b0f9ee3..8d13c75 100644 --- a/shared/simplestreams/simplestreams.go +++ b/shared/simplestreams/simplestreams.go @@ -16,6 +16,7 @@ import ( "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/ioprogress" + "github.com/lxc/lxd/shared/osarch" ) type ssSortImage []shared.ImageInfo @@ -85,12 +86,12 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri for _, product := range s.Products { // Skip unsupported architectures - architecture, err := shared.ArchitectureId(product.Architecture) + architecture, err := osarch.ArchitectureId(product.Architecture) if err != nil { continue } - architectureName, err := shared.ArchitectureName(architecture) + architectureName, err := osarch.ArchitectureName(architecture) if err != nil { continue } @@ -393,7 +394,7 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI return &shared.ImageAlias{Name: name} } - architectureName, _ := shared.ArchitectureGetLocal() + architectureName, _ := osarch.ArchitectureGetLocal() newImages := []shared.ImageInfo{} for _, image := range images { From 46e11b62b7748bcab557d66e387a0cf677dac926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 15 Dec 2016 18:23:48 -0500 Subject: [PATCH 5/5] simplestreams: Don't depend on custom http handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- client.go | 15 ++++++++++++++- lxd/daemon_images.go | 14 ++++++++++++-- shared/simplestreams/simplestreams.go | 21 ++------------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/client.go b/client.go index 0ce204e..fbfa9ed 100644 --- a/client.go +++ b/client.go @@ -339,7 +339,20 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) { } if info.RemoteConfig.Protocol == "simplestreams" { - ss, err := simplestreams.SimpleStreamsClient(c.Remote.Addr, shared.ProxyFromEnvironment) + tlsconfig, err := shared.GetTLSConfig("", "", "", nil) + if err != nil { + return nil, err + } + + tr := &http.Transport{ + TLSClientConfig: tlsconfig, + Dial: shared.RFC3493Dialer, + Proxy: shared.ProxyFromEnvironment, + DisableKeepAlives: true, + } + c.Http.Transport = tr + + ss, err := simplestreams.NewClient(c.Remote.Addr, c.Http) if err != nil { return nil, err } diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go index 5928233..c2dd4f6 100644 --- a/lxd/daemon_images.go +++ b/lxd/daemon_images.go @@ -67,7 +67,12 @@ func imageLoadStreamCache(d *Daemon) error { for url, entry := range imageStreamCache { if entry.ss == nil { - ss, err := simplestreams.SimpleStreamsClient(url, d.proxy) + myhttp, err := d.httpClient("") + if err != nil { + return err + } + + ss, err := simplestreams.NewClient(url, *myhttp) if err != nil { return err } @@ -99,7 +104,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce if entry == nil || entry.expiry.Before(time.Now()) { refresh := func() (*imageStreamCacheEntry, error) { // Setup simplestreams client - ss, err = simplestreams.SimpleStreamsClient(server, d.proxy) + myhttp, err := d.httpClient(certificate) + if err != nil { + return nil, err + } + + ss, err = simplestreams.NewClient(server, *myhttp) if err != nil { return nil, err } diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go index 8d13c75..fd45e4a 100644 --- a/shared/simplestreams/simplestreams.go +++ b/shared/simplestreams/simplestreams.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "net/http" - "net/url" "os" "path/filepath" "sort" @@ -263,25 +262,9 @@ type SimpleStreamsIndexStream struct { Products []string `json:"products"` } -func SimpleStreamsClient(url string, proxy func(*http.Request) (*url.URL, error)) (*SimpleStreams, error) { - // Setup a http client - tlsConfig, err := shared.GetTLSConfig("", "", "", nil) - if err != nil { - return nil, err - } - - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - Dial: shared.RFC3493Dialer, - Proxy: proxy, - } - - myHttp := http.Client{ - Transport: tr, - } - +func NewClient(url string, httpClient http.Client) (*SimpleStreams, error) { return &SimpleStreams{ - http: &myHttp, + http: &httpClient, url: url, cachedManifest: map[string]*SimpleStreamsManifest{}}, nil }
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel