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

Reply via email to