[lxc-devel] [lxd/master] Move all our API structs to shared/api

stgraber on Github lxc-bot at linuxcontainers.org
Thu Dec 22 22:37:35 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161222/4a51cac6/attachment.bin>
-------------- next part --------------
From 271be2f43553bd664d7df3abe90365e8a8c92dfa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 19:24:41 -0500
Subject: [PATCH 1/9] shared: Move REST API to new package: image
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             | 17 ++++----
 lxc/image.go                          | 43 ++++++++++---------
 lxd/containers_post.go                |  5 ++-
 lxd/daemon_images.go                  | 11 ++---
 lxd/db_images.go                      | 57 +++++++++++++-----------
 lxd/db_test.go                        | 15 ++++---
 lxd/images.go                         | 77 +++++++++++----------------------
 lxd/remote.go                         |  4 +-
 lxd/storage.go                        |  3 +-
 shared/api/image.go                   | 81 +++++++++++++++++++++++++++++++++++
 shared/image.go                       | 64 ---------------------------
 shared/simplestreams/simplestreams.go | 55 ++++++++++++------------
 12 files changed, 218 insertions(+), 214 deletions(-)
 create mode 100644 shared/api/image.go
 delete mode 100644 shared/image.go

diff --git a/client.go b/client.go
index b9ca129..1a61dd4 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/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
 	"github.com/lxc/lxd/shared/version"
@@ -1136,7 +1137,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 	return fingerprint, nil
 }
 
-func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) {
+func (c *Client) GetImageInfo(image string) (*api.Image, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
 		return c.simplestreams.GetImageInfo(image)
 	}
@@ -1146,7 +1147,7 @@ func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) {
 		return nil, err
 	}
 
-	info := shared.ImageInfo{}
+	info := api.Image{}
 	if err := json.Unmarshal(resp.Metadata, &info); err != nil {
 		return nil, err
 	}
@@ -1154,7 +1155,7 @@ func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) {
 	return &info, nil
 }
 
-func (c *Client) PutImageInfo(name string, p shared.BriefImageInfo) error {
+func (c *Client) PutImageInfo(name string, p api.ImagePut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1163,7 +1164,7 @@ func (c *Client) PutImageInfo(name string, p shared.BriefImageInfo) error {
 	return err
 }
 
-func (c *Client) ListImages() ([]shared.ImageInfo, error) {
+func (c *Client) ListImages() ([]api.Image, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
 		return c.simplestreams.ListImages()
 	}
@@ -1173,7 +1174,7 @@ func (c *Client) ListImages() ([]shared.ImageInfo, error) {
 		return nil, err
 	}
 
-	var result []shared.ImageInfo
+	var result []api.Image
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
 	}
@@ -1215,7 +1216,7 @@ func (c *Client) DeleteAlias(alias string) error {
 	return err
 }
 
-func (c *Client) ListAliases() (shared.ImageAliases, error) {
+func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
 		return c.simplestreams.ListAliases()
 	}
@@ -1225,7 +1226,7 @@ func (c *Client) ListAliases() (shared.ImageAliases, error) {
 		return nil, err
 	}
 
-	var result shared.ImageAliases
+	var result []api.ImageAliasesEntry
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -1308,7 +1309,7 @@ func (c *Client) GetAlias(alias string) string {
 		return ""
 	}
 
-	var result shared.ImageAliasesEntry
+	var result api.ImageAliasesEntry
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return ""
 	}
diff --git a/lxc/image.go b/lxc/image.go
index 9e23892..b81191d 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
@@ -346,12 +347,12 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		fmt.Printf(i18n.G("Public: %s")+"\n", public)
 		fmt.Printf(i18n.G("Timestamps:") + "\n")
 		const layout = "2006/01/02 15:04 UTC"
-		if info.CreationDate.UTC().Unix() != 0 {
-			fmt.Printf("    "+i18n.G("Created: %s")+"\n", info.CreationDate.UTC().Format(layout))
+		if info.CreatedAt.UTC().Unix() != 0 {
+			fmt.Printf("    "+i18n.G("Created: %s")+"\n", info.CreatedAt.UTC().Format(layout))
 		}
-		fmt.Printf("    "+i18n.G("Uploaded: %s")+"\n", info.UploadDate.UTC().Format(layout))
-		if info.ExpiryDate.UTC().Unix() != 0 {
-			fmt.Printf("    "+i18n.G("Expires: %s")+"\n", info.ExpiryDate.UTC().Format(layout))
+		fmt.Printf("    "+i18n.G("Uploaded: %s")+"\n", info.UploadedAt.UTC().Format(layout))
+		if info.ExpiresAt.UTC().Unix() != 0 {
+			fmt.Printf("    "+i18n.G("Expires: %s")+"\n", info.ExpiresAt.UTC().Format(layout))
 		} else {
 			fmt.Printf("    " + i18n.G("Expires: never") + "\n")
 		}
@@ -364,11 +365,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			fmt.Printf("    - %s\n", alias.Name)
 		}
 		fmt.Printf(i18n.G("Auto update: %s")+"\n", autoUpdate)
-		if info.Source != nil {
+		if info.UpdateSource != nil {
 			fmt.Println(i18n.G("Source:"))
-			fmt.Printf("    Server: %s\n", info.Source.Server)
-			fmt.Printf("    Protocol: %s\n", info.Source.Protocol)
-			fmt.Printf("    Alias: %s\n", info.Source.Alias)
+			fmt.Printf("    Server: %s\n", info.UpdateSource.Server)
+			fmt.Printf("    Protocol: %s\n", info.UpdateSource.Protocol)
+			fmt.Printf("    Alias: %s\n", info.UpdateSource.Alias)
 		}
 		return nil
 
@@ -466,7 +467,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		var images []shared.ImageInfo
+		var images []api.Image
 		allImages, err := d.ListImages()
 		if err != nil {
 			return err
@@ -557,7 +558,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		properties := info.Brief()
+		properties := info.Writable()
 
 		data, err := yaml.Marshal(&properties)
 		fmt.Printf("%s", data)
@@ -576,7 +577,7 @@ func (c *imageCmd) dereferenceAlias(d *lxd.Client, inName string) string {
 	return result
 }
 
-func (c *imageCmd) shortestAlias(list []shared.ImageAlias) string {
+func (c *imageCmd) shortestAlias(list []api.ImageAlias) string {
 	shortest := ""
 	for _, l := range list {
 		if shortest == "" {
@@ -600,7 +601,7 @@ func (c *imageCmd) findDescription(props map[string]string) string {
 	return ""
 }
 
-func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error {
+func (c *imageCmd) showImages(images []api.Image, filters []string) error {
 	switch c.format {
 	case listFormatTable:
 		data := [][]string{}
@@ -622,7 +623,7 @@ func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error
 			}
 
 			const layout = "Jan 2, 2006 at 3:04pm (MST)"
-			uploaded := image.UploadDate.UTC().Format(layout)
+			uploaded := image.UploadedAt.UTC().Format(layout)
 			size := fmt.Sprintf("%.2fMB", float64(image.Size)/1024.0/1024.0)
 			data = append(data, []string{shortest, fp, public, description, image.Architecture, size, uploaded})
 		}
@@ -643,7 +644,7 @@ func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error
 		table.AppendBulk(data)
 		table.Render()
 	case listFormatJSON:
-		data := make([]*shared.ImageInfo, len(images))
+		data := make([]*api.Image, len(images))
 		for i := range images {
 			data[i] = &images[i]
 		}
@@ -659,7 +660,7 @@ func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error
 	return nil
 }
 
-func (c *imageCmd) showAliases(aliases shared.ImageAliases, filters []string) error {
+func (c *imageCmd) showAliases(aliases []api.ImageAliasesEntry, filters []string) error {
 	data := [][]string{}
 	for _, alias := range aliases {
 		if !c.aliasShouldShow(filters, &alias) {
@@ -692,7 +693,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 			return err
 		}
 
-		newdata := shared.BriefImageInfo{}
+		newdata := api.ImagePut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -706,7 +707,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 		return err
 	}
 
-	brief := config.Brief()
+	brief := config.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -720,7 +721,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.BriefImageInfo{}
+		newdata := api.ImagePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.PutImageInfo(image, newdata)
@@ -747,7 +748,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 	return nil
 }
 
-func (c *imageCmd) imageShouldShow(filters []string, state *shared.ImageInfo) bool {
+func (c *imageCmd) imageShouldShow(filters []string, state *api.Image) bool {
 	if len(filters) == 0 {
 		return true
 	}
@@ -806,7 +807,7 @@ func (c *imageCmd) imageShouldShow(filters []string, state *shared.ImageInfo) bo
 	return true
 }
 
-func (c *imageCmd) aliasShouldShow(filters []string, state *shared.ImageAliasesEntry) bool {
+func (c *imageCmd) aliasShouldShow(filters []string, state *api.ImageAliasesEntry) bool {
 	if len(filters) == 0 {
 		return true
 	}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index b7754ea..306c590 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -89,7 +90,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 			return InternalError(err)
 		}
 
-		var image *shared.ImageInfo
+		var image *api.Image
 
 		for _, hash := range hashes {
 			_, img, err := dbImageGet(d.db, hash, false, true)
@@ -97,7 +98,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 				continue
 			}
 
-			if image != nil && img.CreationDate.Before(image.CreationDate) {
+			if image != nil && img.CreatedAt.Before(image.CreatedAt) {
 				continue
 			}
 
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 7324eff..6a8c7ee 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/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
 	"github.com/lxc/lxd/shared/version"
@@ -25,8 +26,8 @@ import (
 
 // Simplestream cache
 type imageStreamCacheEntry struct {
-	Aliases      shared.ImageAliases `yaml:"aliases"`
-	Fingerprints []string            `yaml:"fingerprints"`
+	Aliases      []api.ImageAliasesEntry `yaml:"aliases"`
+	Fingerprints []string                `yaml:"fingerprints"`
 	expiry       time.Time
 	ss           *simplestreams.SimpleStreams
 }
@@ -244,7 +245,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	exporturl := server
 
-	var info shared.ImageInfo
+	var info api.Image
 	info.Fingerprint = fp
 
 	destDir := shared.VarPath("images")
@@ -481,8 +482,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 
 		info.Architecture = imageMeta.Architecture
-		info.CreationDate = time.Unix(imageMeta.CreationDate, 0)
-		info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0)
+		info.CreatedAt = time.Unix(imageMeta.CreationDate, 0)
+		info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0)
 		info.Properties = imageMeta.Properties
 	}
 
diff --git a/lxd/db_images.go b/lxd/db_images.go
index ac515a3..c3aef67 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -7,7 +7,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -76,27 +76,27 @@ func dbImageSourceInsert(db *sql.DB, imageId int, server string, protocol string
 	return err
 }
 
-func dbImageSourceGet(db *sql.DB, imageId int) (int, shared.ImageSource, error) {
+func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
 	q := `SELECT id, server, protocol, certificate, alias FROM images_source WHERE image_id=?`
 
 	id := 0
 	protocolInt := -1
-	result := shared.ImageSource{}
+	result := api.ImageSource{}
 
 	arg1 := []interface{}{imageId}
 	arg2 := []interface{}{&id, &result.Server, &protocolInt, &result.Certificate, &result.Alias}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		if err == sql.ErrNoRows {
-			return -1, shared.ImageSource{}, NoSuchObjectError
+			return -1, api.ImageSource{}, NoSuchObjectError
 		}
 
-		return -1, shared.ImageSource{}, err
+		return -1, api.ImageSource{}, err
 	}
 
 	protocol, found := dbImageSourceProtocol[protocolInt]
 	if !found {
-		return -1, shared.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt)
+		return -1, api.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt)
 	}
 
 	result.Protocol = protocol
@@ -110,12 +110,12 @@ func dbImageSourceGet(db *sql.DB, imageId int) (int, shared.ImageSource, error)
 // pass a shortform and will get the full fingerprint.
 // There can never be more than one image with a given fingerprint, as it is
 // enforced by a UNIQUE constraint in the schema.
-func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *shared.ImageInfo, error) {
+func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *api.Image, error) {
 	var err error
 	var create, expire, used, upload *time.Time // These hold the db-returned times
 
 	// The object we'll actually return
-	image := shared.ImageInfo{}
+	image := api.Image{}
 	id := -1
 	arch := -1
 
@@ -159,27 +159,27 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 
 	// Some of the dates can be nil in the DB, let's process them.
 	if create != nil {
-		image.CreationDate = *create
+		image.CreatedAt = *create
 	} else {
-		image.CreationDate = time.Time{}
+		image.CreatedAt = time.Time{}
 	}
 
 	if expire != nil {
-		image.ExpiryDate = *expire
+		image.ExpiresAt = *expire
 	} else {
-		image.ExpiryDate = time.Time{}
+		image.ExpiresAt = time.Time{}
 	}
 
 	if used != nil {
-		image.LastUsedDate = *used
+		image.LastUsedAt = *used
 	} else {
-		image.LastUsedDate = time.Time{}
+		image.LastUsedAt = time.Time{}
 	}
 
 	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
+	image.UploadedAt = *upload
 
 	// Get the properties
 	q := "SELECT key, value FROM images_properties where image_id=?"
@@ -209,11 +209,11 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 		return -1, nil, err
 	}
 
-	aliases := []shared.ImageAlias{}
+	aliases := []api.ImageAlias{}
 	for _, r := range results {
 		name = r[0].(string)
 		desc = r[0].(string)
-		a := shared.ImageAlias{Name: name, Description: desc}
+		a := api.ImageAlias{Name: name, Description: desc}
 		aliases = append(aliases, a)
 	}
 
@@ -221,7 +221,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 
 	_, source, err := dbImageSourceGet(db, id)
 	if err == nil {
-		image.Source = &source
+		image.UpdateSource = &source
 	}
 
 	return id, &image, nil
@@ -245,7 +245,7 @@ func dbImageDelete(db *sql.DB, id int) error {
 	return nil
 }
 
-func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, shared.ImageAliasesEntry, error) {
+func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, api.ImageAliasesEntry, error) {
 	q := `SELECT images_aliases.id, images.fingerprint, images_aliases.description
 			 FROM images_aliases
 			 INNER JOIN images
@@ -257,19 +257,24 @@ func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, shared
 
 	var fingerprint, description string
 	id := -1
+	entry := api.ImageAliasesEntry{}
 
 	arg1 := []interface{}{name}
 	arg2 := []interface{}{&id, &fingerprint, &description}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		if err == sql.ErrNoRows {
-			return -1, shared.ImageAliasesEntry{}, NoSuchObjectError
+			return -1, entry, NoSuchObjectError
 		}
 
-		return -1, shared.ImageAliasesEntry{}, err
+		return -1, entry, err
 	}
 
-	return id, shared.ImageAliasesEntry{Name: name, Target: fingerprint, Description: description}, nil
+	entry.Name = name
+	entry.Target = fingerprint
+	entry.Description = description
+
+	return id, entry, nil
 }
 
 func dbImageAliasRename(db *sql.DB, id int, name string) error {
@@ -312,7 +317,7 @@ func dbImageLastAccessInit(db *sql.DB, fingerprint string) error {
 	return err
 }
 
-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 {
+func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
@@ -340,7 +345,7 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto
 	}
 	defer stmt.Close()
 
-	_, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate, id)
+	_, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, createdAt, expiresAt, id)
 	if err != nil {
 		tx.Rollback()
 		return err
@@ -369,7 +374,7 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto
 	return nil
 }
 
-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 {
+func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
@@ -397,7 +402,7 @@ func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, a
 	}
 	defer stmt.Close()
 
-	result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate)
+	result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, createdAt, expiresAt)
 	if err != nil {
 		tx.Rollback()
 		return err
diff --git a/lxd/db_test.go b/lxd/db_test.go
index 9e9aa38..ae0e504 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 )
 
@@ -401,7 +402,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 func Test_dbImageGet_finds_image_for_fingerprint(t *testing.T) {
 	var db *sql.DB
 	var err error
-	var result *shared.ImageInfo
+	var result *api.Image
 
 	db = createTestDb(t)
 	defer db.Close()
@@ -420,16 +421,16 @@ func Test_dbImageGet_finds_image_for_fingerprint(t *testing.T) {
 		t.Fatal("Filename should be set.")
 	}
 
-	if result.CreationDate.UTC() != time.Unix(1431547174, 0).UTC() {
-		t.Fatal(fmt.Sprintf("%s != %s", result.CreationDate, time.Unix(1431547174, 0)))
+	if result.CreatedAt.UTC() != time.Unix(1431547174, 0).UTC() {
+		t.Fatal(fmt.Sprintf("%s != %s", result.CreatedAt, time.Unix(1431547174, 0)))
 	}
 
-	if result.ExpiryDate.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived
-		t.Fatal(fmt.Sprintf("%s != %s", result.ExpiryDate, time.Unix(1431547175, 0)))
+	if result.ExpiresAt.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived
+		t.Fatal(fmt.Sprintf("%s != %s", result.ExpiresAt, time.Unix(1431547175, 0)))
 	}
 
-	if result.UploadDate.UTC() != time.Unix(1431547176, 0).UTC() {
-		t.Fatal(fmt.Sprintf("%s != %s", result.UploadDate, time.Unix(1431547176, 0)))
+	if result.UploadedAt.UTC() != time.Unix(1431547176, 0).UTC() {
+		t.Fatal(fmt.Sprintf("%s != %s", result.UploadedAt, time.Unix(1431547176, 0)))
 	}
 }
 
diff --git a/lxd/images.go b/lxd/images.go
index 12e49a1..1d34299 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -23,6 +23,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -208,15 +209,6 @@ type templateEntry struct {
 	Properties map[string]string `yaml:"properties"`
 }
 
-type imagePostReq struct {
-	Filename             string            `json:"filename"`
-	Public               bool              `json:"public"`
-	Source               map[string]string `json:"source"`
-	Properties           map[string]string `json:"properties"`
-	AutoUpdate           bool              `json:"auto_update"`
-	CompressionAlgorithm string            `json:"compression_algorithm"`
-}
-
 type imageMetadata struct {
 	Architecture string                    `yaml:"architecture"`
 	CreationDate int64                     `yaml:"creation_date"`
@@ -229,8 +221,8 @@ type imageMetadata struct {
  * This function takes a container or snapshot from the local image server and
  * exports it as an image.
  */
-func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
-	builddir string) (info shared.ImageInfo, err error) {
+func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
+	builddir string) (info api.Image, err error) {
 
 	info.Properties = map[string]string{}
 	name := req.Source["name"]
@@ -327,7 +319,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
 	return info, nil
 }
 
-func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
+func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	var err error
 	var hash string
 
@@ -356,7 +348,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 
 	// Update the DB record if needed
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties)
+		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return err
 		}
@@ -370,7 +362,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 	return nil
 }
 
-func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
+func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	var err error
 
 	if req.Source["url"] == "" {
@@ -429,7 +421,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 	}
 
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties)
+		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return err
 		}
@@ -444,7 +436,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 }
 
 func getImgPostInfo(d *Daemon, r *http.Request,
-	builddir string, post *os.File) (info shared.ImageInfo, err error) {
+	builddir string, post *os.File) (info api.Image, err error) {
 
 	var imageMeta *imageMetadata
 	logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"})
@@ -618,8 +610,8 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 	}
 
 	info.Architecture = imageMeta.Architecture
-	info.CreationDate = time.Unix(imageMeta.CreationDate, 0)
-	info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0)
+	info.CreatedAt = time.Unix(imageMeta.CreationDate, 0)
+	info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0)
 
 	info.Properties = imageMeta.Properties
 	if len(propHeaders) > 0 {
@@ -634,7 +626,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 	return info, nil
 }
 
-func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]string, err error) {
+func imageBuildFromInfo(d *Daemon, info api.Image) (metadata map[string]string, err error) {
 	err = d.Storage.ImageCreate(info.Fingerprint)
 	if err != nil {
 		os.Remove(shared.VarPath("images", info.Fingerprint))
@@ -651,8 +643,8 @@ func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]s
 		info.Public,
 		info.AutoUpdate,
 		info.Architecture,
-		info.CreationDate,
-		info.ExpiryDate,
+		info.CreatedAt,
+		info.ExpiresAt,
 		info.Properties)
 	if err != nil {
 		return metadata, err
@@ -702,7 +694,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 	decoder := json.NewDecoder(post)
 	imageUpload := false
 
-	req := imagePostReq{}
+	req := api.ImagesPost{}
 	err = decoder.Decode(&req)
 	if err != nil {
 		if r.Header.Get("Content-Type") == "application/json" {
@@ -718,7 +710,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 
 	// Begin background operation
 	run := func(op *operation) error {
-		var info shared.ImageInfo
+		var info api.Image
 
 		// Setup the cleanup function
 		defer cleanup(builddir, post)
@@ -825,7 +817,7 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 	}
 
 	resultString := make([]string, len(results))
-	resultMap := make([]*shared.ImageInfo, len(results))
+	resultMap := make([]*api.Image, len(results))
 	i := 0
 	for _, name := range results {
 		if !recursion {
@@ -903,7 +895,7 @@ func autoUpdateImages(d *Daemon) {
 			continue
 		}
 
-		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedDate)
+		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
 		if err != nil {
 			shared.LogError("Error setting last use date", log.Ctx{"err": err, "fp": hash})
 			continue
@@ -1007,7 +999,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 	return OperationResponse(op)
 }
 
-func doImageGet(d *Daemon, fingerprint string, public bool) (*shared.ImageInfo, Response) {
+func doImageGet(d *Daemon, fingerprint string, public bool) (*api.Image, Response) {
 	_, imgInfo, err := dbImageGet(d.db, fingerprint, public, false)
 	if err != nil {
 		return nil, SmartError(err)
@@ -1064,12 +1056,6 @@ func imageGet(d *Daemon, r *http.Request) Response {
 	return SyncResponseETag(true, info, etag)
 }
 
-type imagePutReq struct {
-	Properties map[string]string `json:"properties"`
-	Public     bool              `json:"public"`
-	AutoUpdate bool              `json:"auto_update"`
-}
-
 func imagePut(d *Daemon, r *http.Request) Response {
 	// Get current value
 	fingerprint := mux.Vars(r)["fingerprint"]
@@ -1085,12 +1071,12 @@ func imagePut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := imagePutReq{}
+	req := api.ImagePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
 
-	err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, req.Properties)
+	err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1126,7 +1112,7 @@ func imagePatch(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	req := imagePutReq{}
+	req := api.ImagePut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -1156,7 +1142,7 @@ func imagePatch(d *Daemon, r *http.Request) Response {
 		info.Properties = properties
 	}
 
-	err = dbImageUpdate(d.db, id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties)
+	err = dbImageUpdate(d.db, id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1166,19 +1152,8 @@ func imagePatch(d *Daemon, r *http.Request) Response {
 
 var imageCmd = Command{name: "images/{fingerprint}", untrustedGet: true, get: imageGet, put: imagePut, delete: imageDelete, patch: imagePatch}
 
-type aliasPostReq struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Target      string `json:"target"`
-}
-
-type aliasPutReq struct {
-	Description string `json:"description"`
-	Target      string `json:"target"`
-}
-
 func aliasesPost(d *Daemon, r *http.Request) Response {
-	req := aliasPostReq{}
+	req := api.ImageAliasesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -1218,7 +1193,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 	responseStr := []string{}
-	responseMap := shared.ImageAliases{}
+	responseMap := []api.ImageAliasesEntry{}
 	for _, res := range results {
 		name = res[0].(string)
 		if !recursion {
@@ -1281,7 +1256,7 @@ func aliasPut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := aliasPutReq{}
+	req := api.ImageAliasesEntryPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -1358,7 +1333,7 @@ func aliasPatch(d *Daemon, r *http.Request) Response {
 func aliasPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	req := aliasPostReq{}
+	req := api.ImageAliasesEntryPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/remote.go b/lxd/remote.go
index ca7158a..0a42483 100644
--- a/lxd/remote.go
+++ b/lxd/remote.go
@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -18,7 +18,7 @@ func remoteGetImageFingerprint(d *Daemon, server string, certificate string, ali
 		return "", err
 	}
 
-	var result shared.ImageAliasesEntry
+	var result api.ImageAliasesEntry
 	if err = json.Unmarshal(resp.Metadata, &result); err != nil {
 		return "", fmt.Errorf("Error reading alias")
 	}
diff --git a/lxd/storage.go b/lxd/storage.go
index 44c131f..774366c 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/logging"
 
@@ -275,7 +276,7 @@ func storageForFilename(d *Daemon, filename string) (storage, error) {
 	return newStorageWithConfig(d, storageType, config)
 }
 
-func storageForImage(d *Daemon, imgInfo *shared.ImageInfo) (storage, error) {
+func storageForImage(d *Daemon, imgInfo *api.Image) (storage, error) {
 	imageFilename := shared.VarPath("images", imgInfo.Fingerprint)
 	return storageForFilename(d, imageFilename)
 }
diff --git a/shared/api/image.go b/shared/api/image.go
new file mode 100644
index 0000000..396aecc
--- /dev/null
+++ b/shared/api/image.go
@@ -0,0 +1,81 @@
+package api
+
+import (
+	"time"
+)
+
+// ImagesPost represents the fields available for a new LXD image
+type ImagesPost struct {
+	ImagePut
+
+	CompressionAlgorithm string            `json:"compression_algorithm"`
+	Filename             string            `json:"filename"`
+	Source               map[string]string `json:"source"`
+}
+
+// ImagePut represents the modifiable fields of a LXD image
+type ImagePut struct {
+	AutoUpdate bool              `json:"auto_update"`
+	Properties map[string]string `json:"properties"`
+	Public     bool              `json:"public"`
+}
+
+// Image represents a LXD image
+type Image struct {
+	ImagePut
+
+	Aliases      []ImageAlias `json:"aliases"`
+	Architecture string       `json:"architecture"`
+	Cached       bool         `json:"cached"`
+	Filename     string       `json:"filename"`
+	Fingerprint  string       `json:"fingerprint"`
+	Size         int64        `json:"size"`
+	UpdateSource *ImageSource `json:"update_source,omitempty"`
+
+	CreatedAt  time.Time `json:"created_at"`
+	ExpiresAt  time.Time `json:"expires_at"`
+	LastUsedAt time.Time `json:"last_used_at"`
+	UploadedAt time.Time `json:"uploaded_at"`
+}
+
+// Convert a full Image struct into a ImagePut struct (filters read-only fields)
+func (img *Image) Writable() ImagePut {
+	return img.ImagePut
+}
+
+// ImageAlias represents an alias from the alias list of a LXD image
+type ImageAlias struct {
+	Name        string `json:"name"`
+	Description string `json:"description"`
+}
+
+// ImageSource represents the source of a LXD image
+type ImageSource struct {
+	Alias       string `json:"alias"`
+	Certificate string `json:"certificate"`
+	Protocol    string `json:"protocol"`
+	Server      string `json:"server"`
+}
+
+// ImageAliasesPost represents a new LXD image alias
+type ImageAliasesPost struct {
+	ImageAliasesEntry
+}
+
+// ImageAliasesEntryPost represents the required fields to rename a LXD image alias
+type ImageAliasesEntryPost struct {
+	Name string `json:"name"`
+}
+
+// ImageAliasesEntryPut represents the modifiable fields of a LXD image alias
+type ImageAliasesEntryPut struct {
+	Description string `json:"description"`
+	Target      string `json:"target"`
+}
+
+// ImageAliasesEntry represents a LXD image alias
+type ImageAliasesEntry struct {
+	ImageAliasesEntryPut
+
+	Name string `json:"name"`
+}
diff --git a/shared/image.go b/shared/image.go
deleted file mode 100644
index e2e39d4..0000000
--- a/shared/image.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package shared
-
-import (
-	"time"
-)
-
-type ImageProperties map[string]string
-
-type ImageAliasesEntry struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Target      string `json:"target"`
-}
-
-type ImageAliases []ImageAliasesEntry
-
-type ImageAlias struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-}
-
-type ImageSource struct {
-	Server      string `json:"server"`
-	Protocol    string `json:"protocol"`
-	Certificate string `json:"certificate"`
-	Alias       string `json:"alias"`
-}
-
-type ImageInfo struct {
-	Aliases      []ImageAlias      `json:"aliases"`
-	Architecture string            `json:"architecture"`
-	Cached       bool              `json:"cached"`
-	Filename     string            `json:"filename"`
-	Fingerprint  string            `json:"fingerprint"`
-	Properties   map[string]string `json:"properties"`
-	Public       bool              `json:"public"`
-	Size         int64             `json:"size"`
-
-	AutoUpdate bool         `json:"auto_update"`
-	Source     *ImageSource `json:"update_source,omitempty"`
-
-	CreationDate time.Time `json:"created_at"`
-	ExpiryDate   time.Time `json:"expires_at"`
-	LastUsedDate time.Time `json:"last_used_at"`
-	UploadDate   time.Time `json:"uploaded_at"`
-}
-
-/*
- * BriefImageInfo contains a subset of the fields in
- * ImageInfo, namely those which a user may update
- */
-type BriefImageInfo struct {
-	AutoUpdate bool              `json:"auto_update"`
-	Properties map[string]string `json:"properties"`
-	Public     bool              `json:"public"`
-}
-
-func (i *ImageInfo) Brief() BriefImageInfo {
-	retstate := BriefImageInfo{
-		AutoUpdate: i.AutoUpdate,
-		Properties: i.Properties,
-		Public:     i.Public}
-	return retstate
-}
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 9be22d7..dd5d206 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -14,11 +14,12 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-type ssSortImage []shared.ImageInfo
+type ssSortImage []api.Image
 
 func (a ssSortImage) Len() int {
 	return len(a)
@@ -31,15 +32,15 @@ func (a ssSortImage) Swap(i, j int) {
 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 {
+			if a[i].CreatedAt.UTC().Unix() == 0 {
 				return true
 			}
 
-			if a[j].CreationDate.UTC().Unix() == 0 {
+			if a[j].CreatedAt.UTC().Unix() == 0 {
 				return false
 			}
 
-			return a[i].CreationDate.UTC().Unix() > a[j].CreationDate.UTC().Unix()
+			return a[i].CreatedAt.UTC().Unix() > a[j].CreatedAt.UTC().Unix()
 		}
 
 		if a[i].Properties["release"] == "" {
@@ -76,10 +77,10 @@ type SimpleStreamsManifest struct {
 	Products map[string]SimpleStreamsManifestProduct `json:"products"`
 }
 
-func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]string) {
+func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 	downloads := map[string][][]string{}
 
-	images := []shared.ImageInfo{}
+	images := []api.Image{}
 	nameLayout := "20060102"
 	eolLayout := "2006-01-02"
 
@@ -169,12 +170,12 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 			}
 			description = fmt.Sprintf("%s (%s)", description, name)
 
-			image := shared.ImageInfo{}
+			image := api.Image{}
 			image.Architecture = architectureName
 			image.Public = true
 			image.Size = size
-			image.CreationDate = creationDate
-			image.UploadDate = creationDate
+			image.CreatedAt = creationDate
+			image.UploadedAt = creationDate
 			image.Filename = filename
 			image.Fingerprint = fingerprint
 			image.Properties = map[string]string{
@@ -189,9 +190,9 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 
 			// Add the provided aliases
 			if product.Aliases != "" {
-				image.Aliases = []shared.ImageAlias{}
+				image.Aliases = []api.ImageAlias{}
 				for _, entry := range strings.Split(product.Aliases, ",") {
-					image.Aliases = append(image.Aliases, shared.ImageAlias{Name: entry})
+					image.Aliases = append(image.Aliases, api.ImageAlias{Name: entry})
 				}
 			}
 
@@ -203,11 +204,11 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 			}
 
 			// Attempt to parse the EOL
-			image.ExpiryDate = time.Unix(0, 0).UTC()
+			image.ExpiresAt = time.Unix(0, 0).UTC()
 			if product.SupportedEOL != "" {
 				eolDate, err := time.Parse(eolLayout, product.SupportedEOL)
 				if err == nil {
-					image.ExpiryDate = eolDate
+					image.ExpiresAt = eolDate
 				}
 			}
 
@@ -278,8 +279,8 @@ type SimpleStreams struct {
 
 	cachedIndex    *SimpleStreamsIndex
 	cachedManifest map[string]*SimpleStreamsManifest
-	cachedImages   []shared.ImageInfo
-	cachedAliases  map[string]*shared.ImageAliasesEntry
+	cachedImages   []api.Image
+	cachedAliases  map[string]*api.ImageAliasesEntry
 }
 
 func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
@@ -356,8 +357,8 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 	return &ssManifest, nil
 }
 
-func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) {
-	aliases := map[string]*shared.ImageAliasesEntry{}
+func (s *SimpleStreams) applyAliases(images []api.Image) ([]api.Image, map[string]*api.ImageAliasesEntry, error) {
+	aliases := map[string]*api.ImageAliasesEntry{}
 
 	sort.Sort(ssSortImage(images))
 
@@ -369,7 +370,7 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 		}
 	}
 
-	addAlias := func(name string, fingerprint string) *shared.ImageAlias {
+	addAlias := func(name string, fingerprint string) *api.ImageAlias {
 		if defaultOS != "" {
 			name = strings.TrimPrefix(name, fmt.Sprintf("%s/", defaultOS))
 		}
@@ -378,17 +379,17 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 			return nil
 		}
 
-		alias := shared.ImageAliasesEntry{}
+		alias := api.ImageAliasesEntry{}
 		alias.Name = name
 		alias.Target = fingerprint
 		aliases[name] = &alias
 
-		return &shared.ImageAlias{Name: name}
+		return &api.ImageAlias{Name: name}
 	}
 
 	architectureName, _ := osarch.ArchitectureGetLocal()
 
-	newImages := []shared.ImageInfo{}
+	newImages := []api.Image{}
 	for _, image := range images {
 		if image.Aliases != nil {
 			// Build a new list of aliases from the provided ones
@@ -418,12 +419,12 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 	return newImages, aliases, nil
 }
 
-func (s *SimpleStreams) getImages() ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) {
+func (s *SimpleStreams) getImages() ([]api.Image, map[string]*api.ImageAliasesEntry, error) {
 	if s.cachedImages != nil && s.cachedAliases != nil {
 		return s.cachedImages, s.cachedAliases, nil
 	}
 
-	images := []shared.ImageInfo{}
+	images := []api.Image{}
 
 	// Load the main index
 	ssIndex, err := s.parseIndex()
@@ -573,13 +574,13 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 	return nil
 }
 
-func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) {
+func (s *SimpleStreams) ListAliases() ([]api.ImageAliasesEntry, error) {
 	_, aliasesMap, err := s.getImages()
 	if err != nil {
 		return nil, err
 	}
 
-	aliases := shared.ImageAliases{}
+	aliases := []api.ImageAliasesEntry{}
 
 	for _, alias := range aliasesMap {
 		aliases = append(aliases, *alias)
@@ -588,7 +589,7 @@ func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) {
 	return aliases, nil
 }
 
-func (s *SimpleStreams) ListImages() ([]shared.ImageInfo, error) {
+func (s *SimpleStreams) ListImages() ([]api.Image, error) {
 	images, _, err := s.getImages()
 	return images, err
 }
@@ -607,7 +608,7 @@ func (s *SimpleStreams) GetAlias(name string) string {
 	return alias.Target
 }
 
-func (s *SimpleStreams) GetImageInfo(fingerprint string) (*shared.ImageInfo, error) {
+func (s *SimpleStreams) GetImageInfo(fingerprint string) (*api.Image, error) {
 	images, _, err := s.getImages()
 	if err != nil {
 		return nil, err

From a660f0a782e112c6043450978e484c06f48fb347 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 14:23:38 -0500
Subject: [PATCH 2/9] shared: Move REST API to new package: certificate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                 |  4 ++--
 lxd/certificates.go       | 24 +++++++++---------------
 shared/api/certificate.go | 28 ++++++++++++++++++++++++++++
 shared/cert.go            |  8 --------
 4 files changed, 39 insertions(+), 25 deletions(-)
 create mode 100644 shared/api/certificate.go

diff --git a/client.go b/client.go
index 1a61dd4..4c9757d 100644
--- a/client.go
+++ b/client.go
@@ -1235,7 +1235,7 @@ func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) {
 	return result, nil
 }
 
-func (c *Client) CertificateList() ([]shared.CertInfo, error) {
+func (c *Client) CertificateList() ([]api.Certificate, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1245,7 +1245,7 @@ func (c *Client) CertificateList() ([]shared.CertInfo, error) {
 		return nil, err
 	}
 
-	var result []shared.CertInfo
+	var result []api.Certificate
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
 	}
diff --git a/lxd/certificates.go b/lxd/certificates.go
index e31a0d2..0f3c42e 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -12,6 +12,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -19,14 +20,14 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 	recursion := d.isRecursionRequest(r)
 
 	if recursion {
-		certResponses := []shared.CertInfo{}
+		certResponses := []api.Certificate{}
 
 		baseCerts, err := dbCertsGet(d.db)
 		if err != nil {
 			return SmartError(err)
 		}
 		for _, baseCert := range baseCerts {
-			resp := shared.CertInfo{}
+			resp := api.Certificate{}
 			resp.Fingerprint = baseCert.Fingerprint
 			resp.Certificate = baseCert.Certificate
 			if baseCert.Type == 1 {
@@ -48,13 +49,6 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, body)
 }
 
-type certificatesPostBody struct {
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
-	Name        string `json:"name"`
-	Password    string `json:"password"`
-}
-
 func readSavedClientCAList(d *Daemon) {
 	d.clientCerts = []x509.Certificate{}
 
@@ -94,7 +88,7 @@ func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 
 func certificatesPost(d *Daemon, r *http.Request) Response {
 	// Parse the request
-	req := certificatesPostBody{}
+	req := api.CertificatesPost{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
@@ -168,8 +162,8 @@ func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
 	return SyncResponseETag(true, cert, cert)
 }
 
-func doCertificateGet(d *Daemon, fingerprint string) (shared.CertInfo, error) {
-	resp := shared.CertInfo{}
+func doCertificateGet(d *Daemon, fingerprint string) (api.Certificate, error) {
+	resp := api.Certificate{}
 
 	dbCertInfo, err := dbCertGet(d.db, fingerprint)
 	if err != nil {
@@ -202,7 +196,7 @@ func certificateFingerprintPut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := shared.CertInfo{}
+	req := api.CertificatePut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
@@ -242,10 +236,10 @@ func certificateFingerprintPatch(d *Daemon, r *http.Request) Response {
 		req.Type = value
 	}
 
-	return doCertificateUpdate(d, fingerprint, req)
+	return doCertificateUpdate(d, fingerprint, req.Writable())
 }
 
-func doCertificateUpdate(d *Daemon, fingerprint string, req shared.CertInfo) Response {
+func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) Response {
 	if req.Type != "client" {
 		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
 	}
diff --git a/shared/api/certificate.go b/shared/api/certificate.go
new file mode 100644
index 0000000..6719d69
--- /dev/null
+++ b/shared/api/certificate.go
@@ -0,0 +1,28 @@
+package api
+
+// CertificatesPost represents the fields of a new LXD certificate
+type CertificatesPost struct {
+	CertificatePut
+
+	Certificate string `json:"certificate"`
+	Password    string `json:"password"`
+}
+
+// CertificatePut represents the modifiable fields of a LXD certificate
+type CertificatePut struct {
+	Name string `json:"name"`
+	Type string `json:"type"`
+}
+
+// Certificate represents a LXD certificate
+type Certificate struct {
+	CertificatePut
+
+	Certificate string `json:"certificate"`
+	Fingerprint string `json:"fingerprint"`
+}
+
+// Convert a full Certificate struct into a CertificatePut struct (filters read-only fields)
+func (cert *Certificate) Writable() CertificatePut {
+	return cert.CertificatePut
+}
diff --git a/shared/cert.go b/shared/cert.go
index d1225e5..b264ee1 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -23,14 +23,6 @@ import (
 	"time"
 )
 
-// CertInfo is the representation of a Certificate in the API.
-type CertInfo struct {
-	Certificate string `json:"certificate"`
-	Fingerprint string `json:"fingerprint"`
-	Name        string `json:"name"`
-	Type        string `json:"type"`
-}
-
 /*
  * Generate a list of names for which the certificate will be valid.
  * This will include the hostname and ip address

From 9980f97220b91f8795a6d13daf0910b8b4461847 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 14:40:07 -0500
Subject: [PATCH 3/9] shared: Move REST API to new package: network
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go             | 20 ++++++++------------
 lxc/network.go        |  7 ++++---
 lxd/db_networks.go    | 26 +++++++++++++++-----------
 lxd/networks.go       | 25 +++++++++++++------------
 shared/api/network.go | 35 +++++++++++++++++++++++++++++++++++
 shared/container.go   |  8 --------
 6 files changed, 75 insertions(+), 46 deletions(-)
 create mode 100644 shared/api/network.go

diff --git a/client.go b/client.go
index 4c9757d..f60c8f6 100644
--- a/client.go
+++ b/client.go
@@ -2788,33 +2788,29 @@ func (c *Client) NetworkCreate(name string, config map[string]string) error {
 	return err
 }
 
-func (c *Client) NetworkGet(name string) (shared.NetworkConfig, error) {
+func (c *Client) NetworkGet(name string) (api.Network, error) {
 	if c.Remote.Public {
-		return shared.NetworkConfig{}, fmt.Errorf("This function isn't supported by public remotes.")
+		return api.Network{}, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
 	resp, err := c.get(fmt.Sprintf("networks/%s", name))
 	if err != nil {
-		return shared.NetworkConfig{}, err
+		return api.Network{}, err
 	}
 
-	network := shared.NetworkConfig{}
+	network := api.Network{}
 	if err := json.Unmarshal(resp.Metadata, &network); err != nil {
-		return shared.NetworkConfig{}, err
+		return api.Network{}, err
 	}
 
 	return network, nil
 }
 
-func (c *Client) NetworkPut(name string, network shared.NetworkConfig) error {
+func (c *Client) NetworkPut(name string, network api.NetworkPut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	if network.Name != name {
-		return fmt.Errorf("Cannot change network name")
-	}
-
 	_, err := c.put(fmt.Sprintf("networks/%s", name), network, Sync)
 	return err
 }
@@ -2828,7 +2824,7 @@ func (c *Client) NetworkDelete(name string) error {
 	return err
 }
 
-func (c *Client) ListNetworks() ([]shared.NetworkConfig, error) {
+func (c *Client) ListNetworks() ([]api.Network, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2838,7 +2834,7 @@ func (c *Client) ListNetworks() ([]shared.NetworkConfig, error) {
 		return nil, err
 	}
 
-	networks := []shared.NetworkConfig{}
+	networks := []api.Network{}
 	if err := json.Unmarshal(resp.Metadata, &networks); err != nil {
 		return nil, err
 	}
diff --git a/lxc/network.go b/lxc/network.go
index 2be6398..777615b 100644
--- a/lxc/network.go
+++ b/lxc/network.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
 )
@@ -303,7 +304,7 @@ func (c *networkCmd) doNetworkEdit(client *lxd.Client, name string) error {
 			return err
 		}
 
-		newdata := shared.NetworkConfig{}
+		newdata := api.NetworkPut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -330,7 +331,7 @@ func (c *networkCmd) doNetworkEdit(client *lxd.Client, name string) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.NetworkConfig{}
+		newdata := api.NetworkPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.NetworkPut(name, newdata)
@@ -458,7 +459,7 @@ func (c *networkCmd) doNetworkSet(client *lxd.Client, name string, args []string
 
 	network.Config[key] = value
 
-	return client.NetworkPut(name, network)
+	return client.NetworkPut(name, network.Writable())
 }
 
 func (c *networkCmd) doNetworkShow(client *lxd.Client, name string) error {
diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 6ee23a3..6ddaa6d 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -7,7 +7,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func dbNetworks(db *sql.DB) ([]string, error) {
@@ -28,11 +28,11 @@ func dbNetworks(db *sql.DB) ([]string, error) {
 	return response, nil
 }
 
-func dbNetworkGet(db *sql.DB, network string) (int64, *shared.NetworkConfig, error) {
+func dbNetworkGet(db *sql.DB, name string) (int64, *api.Network, error) {
 	id := int64(-1)
 
 	q := "SELECT id FROM networks WHERE name=?"
-	arg1 := []interface{}{network}
+	arg1 := []interface{}{name}
 	arg2 := []interface{}{&id}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
@@ -44,15 +44,17 @@ func dbNetworkGet(db *sql.DB, network string) (int64, *shared.NetworkConfig, err
 		return -1, nil, err
 	}
 
-	return id, &shared.NetworkConfig{
-		Name:    network,
+	network := api.Network{
+		Name:    name,
 		Managed: true,
 		Type:    "bridge",
-		Config:  config,
-	}, nil
+	}
+	network.Config = config
+
+	return id, &network, nil
 }
 
-func dbNetworkGetInterface(db *sql.DB, devName string) (int64, *shared.NetworkConfig, error) {
+func dbNetworkGetInterface(db *sql.DB, devName string) (int64, *api.Network, error) {
 	id := int64(-1)
 	name := ""
 	value := ""
@@ -85,12 +87,14 @@ func dbNetworkGetInterface(db *sql.DB, devName string) (int64, *shared.NetworkCo
 		return -1, nil, err
 	}
 
-	return id, &shared.NetworkConfig{
+	network := api.Network{
 		Name:    name,
 		Managed: true,
 		Type:    "bridge",
-		Config:  config,
-	}, nil
+	}
+	network.Config = config
+
+	return id, &network, nil
 }
 
 func dbNetworkConfigGet(db *sql.DB, id int64) (map[string]string, error) {
diff --git a/lxd/networks.go b/lxd/networks.go
index bb5d3c6..8ad97ec 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -16,6 +16,7 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -33,7 +34,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	}
 
 	resultString := []string{}
-	resultMap := []shared.NetworkConfig{}
+	resultMap := []api.Network{}
 	for _, iface := range ifs {
 		if recursion == 0 {
 			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", version.APIVersion, iface))
@@ -54,7 +55,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 }
 
 func networksPost(d *Daemon, r *http.Request) Response {
-	req := shared.NetworkConfig{}
+	req := api.NetworksPost{}
 
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
@@ -161,18 +162,18 @@ func networkGet(d *Daemon, r *http.Request) Response {
 	return SyncResponseETag(true, &n, etag)
 }
 
-func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
+func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 	// Get some information
 	osInfo, _ := net.InterfaceByName(name)
 	_, dbInfo, _ := dbNetworkGet(d.db, name)
 
 	// Sanity check
 	if osInfo == nil && dbInfo == nil {
-		return shared.NetworkConfig{}, os.ErrNotExist
+		return api.Network{}, os.ErrNotExist
 	}
 
 	// Prepare the response
-	n := shared.NetworkConfig{}
+	n := api.Network{}
 	n.Name = name
 	n.UsedBy = []string{}
 	n.Config = map[string]string{}
@@ -180,13 +181,13 @@ func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
 	// Look for containers using the interface
 	cts, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
-		return shared.NetworkConfig{}, err
+		return api.Network{}, err
 	}
 
 	for _, ct := range cts {
 		c, err := containerLoadByName(d, ct)
 		if err != nil {
-			return shared.NetworkConfig{}, err
+			return api.Network{}, err
 		}
 
 		if networkIsInUse(c, n.Name) {
@@ -245,7 +246,7 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 
 func networkPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
-	req := shared.NetworkConfig{}
+	req := api.NetworkPost{}
 
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
@@ -305,7 +306,7 @@ func networkPut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := shared.NetworkConfig{}
+	req := api.NetworkPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -330,7 +331,7 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := shared.NetworkConfig{}
+	req := api.NetworkPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -370,7 +371,7 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newCon
 		return NotFound
 	}
 
-	err = n.Update(shared.NetworkConfig{Config: newConfig})
+	err = n.Update(api.NetworkPut{Config: newConfig})
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1234,7 +1235,7 @@ func (n *network) Stop() error {
 	return nil
 }
 
-func (n *network) Update(newNetwork shared.NetworkConfig) error {
+func (n *network) Update(newNetwork api.NetworkPut) error {
 	err := networkFillAuto(newNetwork.Config)
 	if err != nil {
 		return err
diff --git a/shared/api/network.go b/shared/api/network.go
new file mode 100644
index 0000000..0ce71dd
--- /dev/null
+++ b/shared/api/network.go
@@ -0,0 +1,35 @@
+package api
+
+// NetworksPost represents the fields of a new LXD network
+type NetworksPost struct {
+	NetworkPut
+
+	Managed bool   `json:"managed"`
+	Name    string `json:"name"`
+	Type    string `json:"type"`
+}
+
+// NetworkPost represents the fields required to rename a LXD network
+type NetworkPost struct {
+	Name string `json:"name"`
+}
+
+// NetworkPut represents the modifiable fields of a LXD network
+type NetworkPut struct {
+	Config map[string]string `json:"config"`
+}
+
+// Network represents a LXD network
+type Network struct {
+	NetworkPut
+
+	Managed bool     `json:"managed"`
+	Name    string   `json:"name"`
+	Type    string   `json:"type"`
+	UsedBy  []string `json:"used_by"`
+}
+
+// Convert a full Network struct into a NetworkPut struct (filters read-only fields)
+func (network *Network) Writable() NetworkPut {
+	return network.NetworkPut
+}
diff --git a/shared/container.go b/shared/container.go
index 0af7f44..6fb85aa 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -153,14 +153,6 @@ type ProfileConfig struct {
 	UsedBy      []string                     `json:"used_by"`
 }
 
-type NetworkConfig struct {
-	Name    string            `json:"name"`
-	Config  map[string]string `json:"config"`
-	Managed bool              `json:"managed"`
-	Type    string            `json:"type"`
-	UsedBy  []string          `json:"used_by"`
-}
-
 func IsInt64(value string) error {
 	if value == "" {
 		return nil

From 1797f7ac7cb85f659ea35f3a1e589dfec6d750bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 20:57:36 -0500
Subject: [PATCH 4/9] shared: Move REST API to new package: server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go            |   6 +-
 lxc/config.go        |   9 +--
 lxd/api_1.0.go       | 160 +++++++++++++++++++++++++--------------------------
 shared/api/server.go |  46 +++++++++++++++
 shared/server.go     |  37 ------------
 5 files changed, 133 insertions(+), 125 deletions(-)
 create mode 100644 shared/api/server.go
 delete mode 100644 shared/server.go

diff --git a/client.go b/client.go
index f60c8f6..80c9c23 100644
--- a/client.go
+++ b/client.go
@@ -1691,8 +1691,8 @@ func (c *Client) Delete(name string) (*Response, error) {
 	return c.delete(url, nil, Async)
 }
 
-func (c *Client) ServerStatus() (*shared.ServerState, error) {
-	ss := shared.ServerState{}
+func (c *Client) ServerStatus() (*api.Server, error) {
+	ss := api.Server{}
 
 	resp, err := c.GetServerConfig()
 	if err != nil {
@@ -2362,7 +2362,7 @@ func (c *Client) SetServerConfig(key string, value string) (*Response, error) {
 	return c.put("", ss, Sync)
 }
 
-func (c *Client) UpdateServerConfig(ss shared.BriefServerState) (*Response, error) {
+func (c *Client) UpdateServerConfig(ss api.ServerPut) (*Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/config.go b/lxc/config.go
index 59c67cd..dc7c558 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
@@ -341,7 +342,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 				return err
 			}
 
-			brief := config.Brief()
+			brief := config.Writable()
 			data, err = yaml.Marshal(&brief)
 		} else {
 			var brief shared.BriefContainerInfo
@@ -554,7 +555,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 			return err
 		}
 
-		newdata := shared.BriefServerState{}
+		newdata := api.ServerPut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -570,7 +571,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 		return err
 	}
 
-	brief := config.Brief()
+	brief := config.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -584,7 +585,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.BriefServerState{}
+		newdata := api.ServerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			_, err = client.UpdateServerConfig(newdata)
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 43f692a..d4de314 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/api"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -46,7 +47,7 @@ var api10 = []Command{
 }
 
 func api10Get(d *Daemon, r *http.Request) Response {
-	body := shared.Jmap{
+	srv := api.ServerUntrusted{
 		/* List of API extensions in the order they were added.
 		 *
 		 * The following kind of changes require an addition to api_extensions:
@@ -56,7 +57,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		 *  - New argument inside an existing REST API call
 		 *  - New HTTPs authentication mechanisms or protocols
 		 */
-		"api_extensions": []string{
+		APIExtensions: []string{
 			"storage_zfs_remove_snapshots",
 			"container_host_shutdown_timeout",
 			"container_syscall_filtering",
@@ -84,98 +85,95 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"network_firewall_filtering",
 			"network_routes",
 		},
-
-		"api_status":  "stable",
-		"api_version": version.APIVersion,
+		APIStatus:  "stable",
+		APIVersion: version.APIVersion,
+		Public:     false,
+		Auth:       "untrusted",
 	}
 
-	if d.isTrustedClient(r) {
-		body["auth"] = "trusted"
+	// If untrusted, return now
+	if !d.isTrustedClient(r) {
+		return SyncResponseETag(true, srv, nil)
+	}
 
-		/*
-		 * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
-		 * there is really no better way to do this, which is
-		 * unfortunate. Also, we ditch the more accepted CharsToString
-		 * version in that thread, since it doesn't seem as portable,
-		 * viz. github issue #206.
-		 */
-		uname := syscall.Utsname{}
-		if err := syscall.Uname(&uname); err != nil {
-			return InternalError(err)
-		}
+	srv.Auth = "trusted"
+
+	/*
+	 * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
+	 * there is really no better way to do this, which is
+	 * unfortunate. Also, we ditch the more accepted CharsToString
+	 * version in that thread, since it doesn't seem as portable,
+	 * viz. github issue #206.
+	 */
+	uname := syscall.Utsname{}
+	if err := syscall.Uname(&uname); err != nil {
+		return InternalError(err)
+	}
 
-		kernel := ""
-		for _, c := range uname.Sysname {
-			if c == 0 {
-				break
-			}
-			kernel += string(byte(c))
+	kernel := ""
+	for _, c := range uname.Sysname {
+		if c == 0 {
+			break
 		}
+		kernel += string(byte(c))
+	}
 
-		kernelVersion := ""
-		for _, c := range uname.Release {
-			if c == 0 {
-				break
-			}
-			kernelVersion += string(byte(c))
+	kernelVersion := ""
+	for _, c := range uname.Release {
+		if c == 0 {
+			break
 		}
+		kernelVersion += string(byte(c))
+	}
 
-		kernelArchitecture := ""
-		for _, c := range uname.Machine {
-			if c == 0 {
-				break
-			}
-			kernelArchitecture += string(byte(c))
+	kernelArchitecture := ""
+	for _, c := range uname.Machine {
+		if c == 0 {
+			break
 		}
+		kernelArchitecture += string(byte(c))
+	}
 
-		addresses, err := d.ListenAddresses()
-		if err != nil {
-			return InternalError(err)
-		}
+	addresses, err := d.ListenAddresses()
+	if err != nil {
+		return InternalError(err)
+	}
 
-		var certificate string
-		if len(d.tlsConfig.Certificates) != 0 {
-			certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]}))
-		}
+	var certificate string
+	if len(d.tlsConfig.Certificates) != 0 {
+		certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]}))
+	}
 
-		architectures := []string{}
+	architectures := []string{}
 
-		for _, architecture := range d.architectures {
-			architectureName, err := osarch.ArchitectureName(architecture)
-			if err != nil {
-				return InternalError(err)
-			}
-			architectures = append(architectures, architectureName)
+	for _, architecture := range d.architectures {
+		architectureName, err := osarch.ArchitectureName(architecture)
+		if err != nil {
+			return InternalError(err)
 		}
-
-		env := shared.Jmap{
-			"addresses":           addresses,
-			"architectures":       architectures,
-			"certificate":         certificate,
-			"driver":              "lxc",
-			"driver_version":      lxc.Version(),
-			"kernel":              kernel,
-			"kernel_architecture": kernelArchitecture,
-			"kernel_version":      kernelVersion,
-			"storage":             d.Storage.GetStorageTypeName(),
-			"storage_version":     d.Storage.GetStorageTypeVersion(),
-			"server":              "lxd",
-			"server_pid":          os.Getpid(),
-			"server_version":      version.Version}
-
-		body["environment"] = env
-		body["public"] = false
-		body["config"] = daemonConfigRender()
-	} else {
-		body["auth"] = "untrusted"
-		body["public"] = false
+		architectures = append(architectures, architectureName)
 	}
 
-	return SyncResponseETag(true, body, body["config"])
-}
-
-type apiPut struct {
-	Config shared.Jmap `json:"config"`
+	env := api.ServerEnvironment{
+		Addresses:          addresses,
+		Architectures:      architectures,
+		Certificate:        certificate,
+		Driver:             "lxc",
+		DriverVersion:      lxc.Version(),
+		Kernel:             kernel,
+		KernelArchitecture: kernelArchitecture,
+		KernelVersion:      kernelVersion,
+		Storage:            d.Storage.GetStorageTypeName(),
+		StorageVersion:     d.Storage.GetStorageTypeVersion(),
+		Server:             "lxd",
+		ServerPid:          os.Getpid(),
+		ServerVersion:      version.Version}
+
+	fullSrv := api.Server{ServerUntrusted: srv}
+	fullSrv.Environment = env
+	fullSrv.Config = daemonConfigRender()
+
+	return SyncResponseETag(true, fullSrv, fullSrv.Config)
 }
 
 func api10Put(d *Daemon, r *http.Request) Response {
@@ -189,7 +187,7 @@ func api10Put(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := apiPut{}
+	req := api.ServerPut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
@@ -208,7 +206,7 @@ func api10Patch(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := apiPut{}
+	req := api.ServerPut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
@@ -227,7 +225,7 @@ func api10Patch(d *Daemon, r *http.Request) Response {
 	return doApi10Update(d, oldConfig, req)
 }
 
-func doApi10Update(d *Daemon, oldConfig map[string]string, req apiPut) Response {
+func doApi10Update(d *Daemon, oldConfig map[string]string, req api.ServerPut) Response {
 	// Deal with special keys
 	for k, v := range req.Config {
 		config := daemonConfig[k]
diff --git a/shared/api/server.go b/shared/api/server.go
new file mode 100644
index 0000000..f453cf1
--- /dev/null
+++ b/shared/api/server.go
@@ -0,0 +1,46 @@
+package api
+
+// ServerEnvironment represents the read-only environment fields of a LXD server
+type ServerEnvironment struct {
+	Addresses              []string `json:"addresses"`
+	Architectures          []string `json:"architectures"`
+	Certificate            string   `json:"certificate"`
+	CertificateFingerprint string   `json:"certificate_fingerprint"`
+	Driver                 string   `json:"driver"`
+	DriverVersion          string   `json:"driver_version"`
+	Kernel                 string   `json:"kernel"`
+	KernelArchitecture     string   `json:"kernel_architecture"`
+	KernelVersion          string   `json:"kernel_version"`
+	Server                 string   `json:"server"`
+	ServerPid              int      `json:"server_pid"`
+	ServerVersion          string   `json:"server_version"`
+	Storage                string   `json:"storage"`
+	StorageVersion         string   `json:"storage_version"`
+}
+
+// ServerPut represents the modifiable fields of a LXD server configuration
+type ServerPut struct {
+	Config map[string]interface{} `json:"config"`
+}
+
+// ServerUntrusted represents a LXD server for an untrusted client
+type ServerUntrusted struct {
+	APIExtensions []string `json:"api_extensions"`
+	APIStatus     string   `json:"api_status"`
+	APIVersion    string   `json:"api_version"`
+	Auth          string   `json:"auth"`
+	Public        bool     `json:"public"`
+}
+
+// Server represents a LXD server
+type Server struct {
+	ServerPut
+	ServerUntrusted
+
+	Environment ServerEnvironment `json:"environment"`
+}
+
+// Convert a full Server struct into a ServerPut struct (filters read-only fields)
+func (srv *Server) Writable() ServerPut {
+	return srv.ServerPut
+}
diff --git a/shared/server.go b/shared/server.go
deleted file mode 100644
index 96044da..0000000
--- a/shared/server.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package shared
-
-type ServerStateEnvironment struct {
-	Addresses              []string `json:"addresses"`
-	Architectures          []string `json:"architectures"`
-	Certificate            string   `json:"certificate"`
-	CertificateFingerprint string   `json:"certificate_fingerprint"`
-	Driver                 string   `json:"driver"`
-	DriverVersion          string   `json:"driver_version"`
-	Kernel                 string   `json:"kernel"`
-	KernelArchitecture     string   `json:"kernel_architecture"`
-	KernelVersion          string   `json:"kernel_version"`
-	Server                 string   `json:"server"`
-	ServerPid              int      `json:"server_pid"`
-	ServerVersion          string   `json:"server_version"`
-	Storage                string   `json:"storage"`
-	StorageVersion         string   `json:"storage_version"`
-}
-
-type ServerState struct {
-	APIExtensions []string               `json:"api_extensions"`
-	APIStatus     string                 `json:"api_status"`
-	APIVersion    string                 `json:"api_version"`
-	Auth          string                 `json:"auth"`
-	Environment   ServerStateEnvironment `json:"environment"`
-	Config        map[string]interface{} `json:"config"`
-	Public        bool                   `json:"public"`
-}
-
-type BriefServerState struct {
-	Config map[string]interface{} `json:"config"`
-}
-
-func (c *ServerState) Brief() BriefServerState {
-	retstate := BriefServerState{Config: c.Config}
-	return retstate
-}

From aa8cecd978ad0a0c3ceef479419e04ebab80652b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 22:41:47 -0500
Subject: [PATCH 5/9] shared: Move REST API to new package: status
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                 | 12 ++++++------
 lxc/action.go             |  3 ++-
 lxc/delete.go             |  5 +++--
 lxc/init.go               |  4 ++--
 lxc/publish.go            |  5 +++--
 lxd/container_lxc.go      | 27 +++++++++++++-------------
 lxd/containers_get.go     |  5 +++--
 lxd/operations.go         | 29 ++++++++++++++--------------
 lxd/response.go           | 27 +++++++++++++-------------
 shared/api/status_code.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++
 shared/container.go       | 10 ++++++----
 shared/operation.go       |  4 +++-
 shared/status.go          | 49 -----------------------------------------------
 13 files changed, 120 insertions(+), 109 deletions(-)
 create mode 100644 shared/api/status_code.go
 delete mode 100644 shared/status.go

diff --git a/client.go b/client.go
index 80c9c23..fd6c177 100644
--- a/client.go
+++ b/client.go
@@ -1643,11 +1643,11 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		return -1, err
 	}
 
-	if op.StatusCode == shared.Failure {
+	if op.StatusCode == api.Failure {
 		return -1, fmt.Errorf(op.Err)
 	}
 
-	if op.StatusCode != shared.Success {
+	if op.StatusCode != api.Success {
 		return -1, fmt.Errorf("got bad op status %s", op.Status)
 	}
 
@@ -2241,7 +2241,7 @@ func (c *Client) WaitForSuccess(waitURL string) error {
 		return err
 	}
 
-	if op.StatusCode == shared.Success {
+	if op.StatusCode == api.Success {
 		return nil
 	}
 
@@ -2254,7 +2254,7 @@ func (c *Client) WaitForSuccessOp(waitURL string) (*shared.Operation, error) {
 		return nil, err
 	}
 
-	if op.StatusCode == shared.Success {
+	if op.StatusCode == api.Success {
 		return op, nil
 	}
 
@@ -2723,11 +2723,11 @@ func (c *Client) AsyncWaitMeta(resp *Response) (*shared.Jmap, error) {
 		return nil, err
 	}
 
-	if op.StatusCode == shared.Failure {
+	if op.StatusCode == api.Failure {
 		return nil, fmt.Errorf(op.Err)
 	}
 
-	if op.StatusCode != shared.Success {
+	if op.StatusCode != api.Success {
 		return nil, fmt.Errorf("got bad op status %s", op.Status)
 	}
 
diff --git a/lxc/action.go b/lxc/action.go
index 15b3569..ab66cfe 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -76,7 +77,7 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 			}
 
 			// "start" for a frozen container means "unfreeze"
-			if current.StatusCode == shared.Frozen {
+			if current.StatusCode == api.Frozen {
 				c.action = shared.Unfreeze
 			}
 
diff --git a/lxc/delete.go b/lxc/delete.go
index 866e5a3..6900fb3 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -87,7 +88,7 @@ func (c *deleteCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		if ct.StatusCode != 0 && ct.StatusCode != shared.Stopped {
+		if ct.StatusCode != 0 && ct.StatusCode != api.Stopped {
 			if !c.force {
 				return fmt.Errorf(i18n.G("The container is currently running, stop it first or pass --force."))
 			}
@@ -102,7 +103,7 @@ func (c *deleteCmd) run(config *lxd.Config, args []string) error {
 				return err
 			}
 
-			if op.StatusCode == shared.Failure {
+			if op.StatusCode == api.Failure {
 				return fmt.Errorf(i18n.G("Stopping container failed!"))
 			}
 
diff --git a/lxc/init.go b/lxc/init.go
index 20d062b..c94c225 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -260,7 +260,7 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer,
 			return
 		}
 
-		if shared.StatusCode(md["status_code"].(float64)).IsFinal() {
+		if api.StatusCode(md["status_code"].(float64)).IsFinal() {
 			return
 		}
 
diff --git a/lxc/publish.go b/lxc/publish.go
index d45ed14..38c6bd6 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 
@@ -83,7 +84,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		wasRunning := ct.StatusCode != 0 && ct.StatusCode != shared.Stopped
+		wasRunning := ct.StatusCode != 0 && ct.StatusCode != api.Stopped
 		wasEphemeral := ct.Ephemeral
 
 		if wasRunning {
@@ -109,7 +110,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 				return err
 			}
 
-			if op.StatusCode == shared.Failure {
+			if op.StatusCode == api.Failure {
 				return fmt.Errorf(i18n.G("Stopping container failed!"))
 			}
 			defer s.Action(cName, shared.Start, -1, true, false)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a660370..f4b1221 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -164,17 +165,17 @@ func lxcValidConfig(rawLxc string) error {
 	return nil
 }
 
-func lxcStatusCode(state lxc.State) shared.StatusCode {
-	return map[int]shared.StatusCode{
-		1: shared.Stopped,
-		2: shared.Starting,
-		3: shared.Running,
-		4: shared.Stopping,
-		5: shared.Aborting,
-		6: shared.Freezing,
-		7: shared.Frozen,
-		8: shared.Thawed,
-		9: shared.Error,
+func lxcStatusCode(state lxc.State) api.StatusCode {
+	return map[int]api.StatusCode{
+		1: api.Stopped,
+		2: api.Starting,
+		3: api.Running,
+		4: api.Stopping,
+		5: api.Aborting,
+		6: api.Freezing,
+		7: api.Frozen,
+		8: api.Thawed,
+		9: api.Error,
 	}[int(state)]
 }
 
@@ -6278,12 +6279,12 @@ func (c *containerLXC) State() string {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
-		return "BROKEN"
+		return "Broken"
 	}
 
 	state, err := c.getLxcState()
 	if err != nil {
-		return shared.Error.String()
+		return api.Error.String()
 	}
 	return state.String()
 }
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 5c54107..d3719ec 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -50,8 +51,8 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 			if err != nil {
 				c = &shared.ContainerInfo{
 					Name:       container,
-					Status:     shared.Error.String(),
-					StatusCode: shared.Error}
+					Status:     api.Error.String(),
+					StatusCode: api.Error}
 			}
 			resultList = append(resultList, c)
 		}
diff --git a/lxd/operations.go b/lxd/operations.go
index 52df1ec..dec968d 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -12,6 +12,7 @@ import (
 	"github.com/pborman/uuid"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -39,7 +40,7 @@ type operation struct {
 	class     operationClass
 	createdAt time.Time
 	updatedAt time.Time
-	status    shared.StatusCode
+	status    api.StatusCode
 	url       string
 	resources map[string][]string
 	metadata  map[string]interface{}
@@ -97,21 +98,21 @@ func (op *operation) done() {
 }
 
 func (op *operation) Run() (chan error, error) {
-	if op.status != shared.Pending {
+	if op.status != api.Pending {
 		return nil, fmt.Errorf("Only pending operations can be started")
 	}
 
 	chanRun := make(chan error, 1)
 
 	op.lock.Lock()
-	op.status = shared.Running
+	op.status = api.Running
 
 	if op.onRun != nil {
 		go func(op *operation, chanRun chan error) {
 			err := op.onRun(op)
 			if err != nil {
 				op.lock.Lock()
-				op.status = shared.Failure
+				op.status = api.Failure
 				op.err = SmartError(err).String()
 				op.lock.Unlock()
 				op.done()
@@ -125,7 +126,7 @@ func (op *operation) Run() (chan error, error) {
 			}
 
 			op.lock.Lock()
-			op.status = shared.Success
+			op.status = api.Success
 			op.lock.Unlock()
 			op.done()
 			chanRun <- nil
@@ -147,7 +148,7 @@ func (op *operation) Run() (chan error, error) {
 }
 
 func (op *operation) Cancel() (chan error, error) {
-	if op.status != shared.Running {
+	if op.status != api.Running {
 		return nil, fmt.Errorf("Only running operations can be cancelled")
 	}
 
@@ -159,11 +160,11 @@ func (op *operation) Cancel() (chan error, error) {
 
 	op.lock.Lock()
 	oldStatus := op.status
-	op.status = shared.Cancelling
+	op.status = api.Cancelling
 	op.lock.Unlock()
 
 	if op.onCancel != nil {
-		go func(op *operation, oldStatus shared.StatusCode, chanCancel chan error) {
+		go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) {
 			err := op.onCancel(op)
 			if err != nil {
 				op.lock.Lock()
@@ -178,7 +179,7 @@ func (op *operation) Cancel() (chan error, error) {
 			}
 
 			op.lock.Lock()
-			op.status = shared.Cancelled
+			op.status = api.Cancelled
 			op.lock.Unlock()
 			op.done()
 			chanCancel <- nil
@@ -195,7 +196,7 @@ func (op *operation) Cancel() (chan error, error) {
 
 	if op.onCancel == nil {
 		op.lock.Lock()
-		op.status = shared.Cancelled
+		op.status = api.Cancelled
 		op.lock.Unlock()
 		op.done()
 		chanCancel <- nil
@@ -213,7 +214,7 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 		return nil, fmt.Errorf("Only websocket operations can be connected")
 	}
 
-	if op.status != shared.Running {
+	if op.status != api.Running {
 		return nil, fmt.Errorf("Only running operations can be connected")
 	}
 
@@ -308,7 +309,7 @@ func (op *operation) WaitFinal(timeout int) (bool, error) {
 }
 
 func (op *operation) UpdateResources(opResources map[string][]string) error {
-	if op.status != shared.Pending && op.status != shared.Running {
+	if op.status != api.Pending && op.status != api.Running {
 		return fmt.Errorf("Only pending or running operations can be updated")
 	}
 
@@ -329,7 +330,7 @@ func (op *operation) UpdateResources(opResources map[string][]string) error {
 }
 
 func (op *operation) UpdateMetadata(opMetadata interface{}) error {
-	if op.status != shared.Pending && op.status != shared.Running {
+	if op.status != api.Pending && op.status != api.Running {
 		return fmt.Errorf("Only pending or running operations can be updated")
 	}
 
@@ -365,7 +366,7 @@ func operationCreate(opClass operationClass, opResources map[string][]string, op
 	op.class = opClass
 	op.createdAt = time.Now()
 	op.updatedAt = op.createdAt
-	op.status = shared.Pending
+	op.status = api.Pending
 	op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
 	op.resources = opResources
 	op.chanDone = make(chan error)
diff --git a/lxd/response.go b/lxd/response.go
index ec3ea59..062e8e1 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -15,21 +15,22 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 type syncResp struct {
-	Type       lxd.ResponseType  `json:"type"`
-	Status     string            `json:"status"`
-	StatusCode shared.StatusCode `json:"status_code"`
-	Metadata   interface{}       `json:"metadata"`
+	Type       lxd.ResponseType `json:"type"`
+	Status     string           `json:"status"`
+	StatusCode api.StatusCode   `json:"status_code"`
+	Metadata   interface{}      `json:"metadata"`
 }
 
 type asyncResp struct {
-	Type       lxd.ResponseType  `json:"type"`
-	Status     string            `json:"status"`
-	StatusCode shared.StatusCode `json:"status_code"`
-	Metadata   interface{}       `json:"metadata"`
-	Operation  string            `json:"operation"`
+	Type       lxd.ResponseType `json:"type"`
+	Status     string           `json:"status"`
+	StatusCode api.StatusCode   `json:"status_code"`
+	Metadata   interface{}      `json:"metadata"`
+	Operation  string           `json:"operation"`
 }
 
 type Response interface {
@@ -56,9 +57,9 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
 	}
 
 	// Prepare the JSON response
-	status := shared.Success
+	status := api.Success
 	if !r.success {
-		status = shared.Failure
+		status = api.Failure
 	}
 
 	if r.headers != nil {
@@ -233,8 +234,8 @@ func (r *operationResponse) Render(w http.ResponseWriter) error {
 
 	body := asyncResp{
 		Type:       lxd.Async,
-		Status:     shared.OperationCreated.String(),
-		StatusCode: shared.OperationCreated,
+		Status:     api.OperationCreated.String(),
+		StatusCode: api.OperationCreated,
 		Operation:  url,
 		Metadata:   md}
 
diff --git a/shared/api/status_code.go b/shared/api/status_code.go
new file mode 100644
index 0000000..74ec4eb
--- /dev/null
+++ b/shared/api/status_code.go
@@ -0,0 +1,49 @@
+package api
+
+type StatusCode int
+
+const (
+	OperationCreated StatusCode = 100
+	Started          StatusCode = 101
+	Stopped          StatusCode = 102
+	Running          StatusCode = 103
+	Cancelling       StatusCode = 104
+	Pending          StatusCode = 105
+	Starting         StatusCode = 106
+	Stopping         StatusCode = 107
+	Aborting         StatusCode = 108
+	Freezing         StatusCode = 109
+	Frozen           StatusCode = 110
+	Thawed           StatusCode = 111
+	Error            StatusCode = 112
+
+	Success StatusCode = 200
+
+	Failure   StatusCode = 400
+	Cancelled StatusCode = 401
+)
+
+func (o StatusCode) String() string {
+	return map[StatusCode]string{
+		OperationCreated: "Operation created",
+		Started:          "Started",
+		Stopped:          "Stopped",
+		Running:          "Running",
+		Cancelling:       "Cancelling",
+		Pending:          "Pending",
+		Success:          "Success",
+		Failure:          "Failure",
+		Cancelled:        "Cancelled",
+		Starting:         "Starting",
+		Stopping:         "Stopping",
+		Aborting:         "Aborting",
+		Freezing:         "Freezing",
+		Frozen:           "Frozen",
+		Thawed:           "Thawed",
+		Error:            "Error",
+	}[o]
+}
+
+func (o StatusCode) IsFinal() bool {
+	return int(o) >= 200
+}
diff --git a/shared/container.go b/shared/container.go
index 6fb85aa..5d0052b 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -6,11 +6,13 @@ import (
 	"strings"
 	"syscall"
 	"time"
+
+	"github.com/lxc/lxd/shared/api"
 )
 
 type ContainerState struct {
 	Status     string                           `json:"status"`
-	StatusCode StatusCode                       `json:"status_code"`
+	StatusCode api.StatusCode                   `json:"status_code"`
 	CPU        ContainerStateCPU                `json:"cpu"`
 	Disk       map[string]ContainerStateDisk    `json:"disk"`
 	Memory     ContainerStateMemory             `json:"memory"`
@@ -91,14 +93,14 @@ type ContainerInfo struct {
 	Profiles        []string                     `json:"profiles"`
 	Stateful        bool                         `json:"stateful"`
 	Status          string                       `json:"status"`
-	StatusCode      StatusCode                   `json:"status_code"`
+	StatusCode      api.StatusCode               `json:"status_code"`
 }
 
 func (c ContainerInfo) IsActive() bool {
 	switch c.StatusCode {
-	case Stopped:
+	case api.Stopped:
 		return false
-	case Error:
+	case api.Error:
 		return false
 	default:
 		return true
diff --git a/shared/operation.go b/shared/operation.go
index 121be2e..7abe917 100644
--- a/shared/operation.go
+++ b/shared/operation.go
@@ -2,6 +2,8 @@ package shared
 
 import (
 	"time"
+
+	"github.com/lxc/lxd/shared/api"
 )
 
 type Operation struct {
@@ -10,7 +12,7 @@ type Operation struct {
 	CreatedAt  time.Time           `json:"created_at"`
 	UpdatedAt  time.Time           `json:"updated_at"`
 	Status     string              `json:"status"`
-	StatusCode StatusCode          `json:"status_code"`
+	StatusCode api.StatusCode      `json:"status_code"`
 	Resources  map[string][]string `json:"resources"`
 	Metadata   *Jmap               `json:"metadata"`
 	MayCancel  bool                `json:"may_cancel"`
diff --git a/shared/status.go b/shared/status.go
deleted file mode 100644
index 7651f5c..0000000
--- a/shared/status.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package shared
-
-type StatusCode int
-
-const (
-	OperationCreated StatusCode = 100
-	Started          StatusCode = 101
-	Stopped          StatusCode = 102
-	Running          StatusCode = 103
-	Cancelling       StatusCode = 104
-	Pending          StatusCode = 105
-	Starting         StatusCode = 106
-	Stopping         StatusCode = 107
-	Aborting         StatusCode = 108
-	Freezing         StatusCode = 109
-	Frozen           StatusCode = 110
-	Thawed           StatusCode = 111
-	Error            StatusCode = 112
-
-	Success StatusCode = 200
-
-	Failure   StatusCode = 400
-	Cancelled StatusCode = 401
-)
-
-func (o StatusCode) String() string {
-	return map[StatusCode]string{
-		OperationCreated: "Operation created",
-		Started:          "Started",
-		Stopped:          "Stopped",
-		Running:          "Running",
-		Cancelling:       "Cancelling",
-		Pending:          "Pending",
-		Success:          "Success",
-		Failure:          "Failure",
-		Cancelled:        "Cancelled",
-		Starting:         "Starting",
-		Stopping:         "Stopping",
-		Aborting:         "Aborting",
-		Freezing:         "Freezing",
-		Frozen:           "Frozen",
-		Thawed:           "Thawed",
-		Error:            "Error",
-	}[o]
-}
-
-func (o StatusCode) IsFinal() bool {
-	return int(o) >= 200
-}

From 2b9e2be79f42b09012d29ce79af972e404362add Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Dec 2016 14:47:50 -0500
Subject: [PATCH 6/9] shared: Move REST API to new package: operation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               | 33 +++++++++++++++++----------------
 lxc/copy.go             |  2 +-
 lxd/operations.go       | 12 +++++-------
 shared/api/operation.go | 19 +++++++++++++++++++
 shared/operation.go     | 20 --------------------
 5 files changed, 42 insertions(+), 44 deletions(-)
 create mode 100644 shared/api/operation.go
 delete mode 100644 shared/operation.go

diff --git a/client.go b/client.go
index fd6c177..3ff0c19 100644
--- a/client.go
+++ b/client.go
@@ -90,8 +90,8 @@ func (r *Response) MetadataAsMap() (*shared.Jmap, error) {
 	return &ret, nil
 }
 
-func (r *Response) MetadataAsOperation() (*shared.Operation, error) {
-	op := shared.Operation{}
+func (r *Response) MetadataAsOperation() (*api.Operation, error) {
+	op := api.Operation{}
 	if err := json.Unmarshal(r.Metadata, &op); err != nil {
 		return nil, err
 	}
@@ -660,7 +660,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 			return err
 		}
 
-		secret, err = op.Metadata.GetString("secret")
+		secret, err = shared.Jmap(op.Metadata).GetString("secret")
 		if err != nil {
 			return err
 		}
@@ -730,7 +730,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		}
 
 		if op.Metadata != nil {
-			value, err := op.Metadata.GetString("fingerprint")
+			value, err := shared.Jmap(op.Metadata).GetString("fingerprint")
 			if err == nil {
 				fingerprint = value
 			}
@@ -957,7 +957,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 		return "", fmt.Errorf("Missing operation metadata")
 	}
 
-	fingerprint, err := op.Metadata.GetString("fingerprint")
+	fingerprint, err := shared.Jmap(op.Metadata).GetString("fingerprint")
 	if err != nil {
 		return "", err
 	}
@@ -1115,12 +1115,12 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		return "", err
 	}
 
-	jmap, err := c.AsyncWaitMeta(resp)
+	meta, err := c.AsyncWaitMeta(resp)
 	if err != nil {
 		return "", err
 	}
 
-	fingerprint, err := jmap.GetString("fingerprint")
+	fingerprint, err := shared.Jmap(meta).GetString("fingerprint")
 	if err != nil {
 		return "", err
 	}
@@ -1376,7 +1376,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 					return nil, err
 				}
 
-				secret, err = op.Metadata.GetString("secret")
+				secret, err = shared.Jmap(op.Metadata).GetString("secret")
 				if err != nil {
 					return nil, err
 				}
@@ -1568,7 +1568,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		return -1, err
 	}
 
-	fds, err = op.Metadata.GetMap("fds")
+	fds, err = shared.Jmap(op.Metadata).GetMap("fds")
 	if err != nil {
 		return -1, err
 	}
@@ -1655,7 +1655,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		return -1, fmt.Errorf("no metadata received")
 	}
 
-	return op.Metadata.GetInt("return")
+	return shared.Jmap(op.Metadata).GetInt("return")
 }
 
 func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*Response, error) {
@@ -2104,7 +2104,8 @@ func (c *Client) MigrateFrom(name string, operation string, certificate string,
 		if err != nil {
 			return nil, err
 		}
-		for k, v := range *op.Metadata {
+
+		for k, v := range op.Metadata {
 			destSecrets[k] = v.(string)
 		}
 
@@ -2216,7 +2217,7 @@ func (c *Client) Rename(name string, newName string) (*Response, error) {
 }
 
 /* Wait for an operation */
-func (c *Client) WaitFor(waitURL string) (*shared.Operation, error) {
+func (c *Client) WaitFor(waitURL string) (*api.Operation, error) {
 	if len(waitURL) < 1 {
 		return nil, fmt.Errorf("invalid wait url %s", waitURL)
 	}
@@ -2248,7 +2249,7 @@ func (c *Client) WaitForSuccess(waitURL string) error {
 	return fmt.Errorf(op.Err)
 }
 
-func (c *Client) WaitForSuccessOp(waitURL string) (*shared.Operation, error) {
+func (c *Client) WaitForSuccessOp(waitURL string) (*api.Operation, error) {
 	op, err := c.WaitFor(waitURL)
 	if err != nil {
 		return nil, err
@@ -2717,7 +2718,7 @@ func (c *Client) ProfileCopy(name, newname string, dest *Client) error {
 	return err
 }
 
-func (c *Client) AsyncWaitMeta(resp *Response) (*shared.Jmap, error) {
+func (c *Client) AsyncWaitMeta(resp *Response) (map[string]interface{}, error) {
 	op, err := c.WaitFor(resp.Operation)
 	if err != nil {
 		return nil, err
@@ -2754,12 +2755,12 @@ func (c *Client) ImageFromContainer(cname string, public bool, aliases []string,
 		return "", err
 	}
 
-	jmap, err := c.AsyncWaitMeta(resp)
+	meta, err := c.AsyncWaitMeta(resp)
 	if err != nil {
 		return "", err
 	}
 
-	fingerprint, err := jmap.GetString("fingerprint")
+	fingerprint, err := shared.Jmap(meta).GetString("fingerprint")
 	if err != nil {
 		return "", err
 	}
diff --git a/lxc/copy.go b/lxc/copy.go
index 9f8437a..4c98735 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -189,7 +189,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		return err
 	}
 
-	for k, v := range *op.Metadata {
+	for k, v := range op.Metadata {
 		secrets[k] = v.(string)
 	}
 
diff --git a/lxd/operations.go b/lxd/operations.go
index dec968d..4ff6e0b 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -246,7 +246,7 @@ func (op *operation) mayCancel() bool {
 	return op.onCancel != nil || op.class == operationClassToken
 }
 
-func (op *operation) Render() (string, *shared.Operation, error) {
+func (op *operation) Render() (string, *api.Operation, error) {
 	// Setup the resource URLs
 	resources := op.resources
 	if resources != nil {
@@ -261,9 +261,7 @@ func (op *operation) Render() (string, *shared.Operation, error) {
 		resources = tmpResources
 	}
 
-	md := shared.Jmap(op.metadata)
-
-	return op.url, &shared.Operation{
+	return op.url, &api.Operation{
 		Id:         op.id,
 		Class:      op.class.String(),
 		CreatedAt:  op.createdAt,
@@ -271,7 +269,7 @@ func (op *operation) Render() (string, *shared.Operation, error) {
 		Status:     op.status.String(),
 		StatusCode: op.status,
 		Resources:  resources,
-		Metadata:   &md,
+		Metadata:   op.metadata,
 		MayCancel:  op.mayCancel(),
 		Err:        op.err,
 	}, nil
@@ -473,7 +471,7 @@ func operationsAPIGet(d *Daemon, r *http.Request) Response {
 		_, ok := md[status]
 		if !ok {
 			if recursion {
-				md[status] = make([]*shared.Operation, 0)
+				md[status] = make([]*api.Operation, 0)
 			} else {
 				md[status] = make([]string, 0)
 			}
@@ -489,7 +487,7 @@ func operationsAPIGet(d *Daemon, r *http.Request) Response {
 			continue
 		}
 
-		md[status] = append(md[status].([]*shared.Operation), body)
+		md[status] = append(md[status].([]*api.Operation), body)
 	}
 
 	return SyncResponse(true, md)
diff --git a/shared/api/operation.go b/shared/api/operation.go
new file mode 100644
index 0000000..5529f4d
--- /dev/null
+++ b/shared/api/operation.go
@@ -0,0 +1,19 @@
+package api
+
+import (
+	"time"
+)
+
+// Operation represents a LXD background operation
+type Operation struct {
+	Id         string                 `json:"id"`
+	Class      string                 `json:"class"`
+	CreatedAt  time.Time              `json:"created_at"`
+	UpdatedAt  time.Time              `json:"updated_at"`
+	Status     string                 `json:"status"`
+	StatusCode StatusCode             `json:"status_code"`
+	Resources  map[string][]string    `json:"resources"`
+	Metadata   map[string]interface{} `json:"metadata"`
+	MayCancel  bool                   `json:"may_cancel"`
+	Err        string                 `json:"err"`
+}
diff --git a/shared/operation.go b/shared/operation.go
deleted file mode 100644
index 7abe917..0000000
--- a/shared/operation.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package shared
-
-import (
-	"time"
-
-	"github.com/lxc/lxd/shared/api"
-)
-
-type Operation struct {
-	Id         string              `json:"id"`
-	Class      string              `json:"class"`
-	CreatedAt  time.Time           `json:"created_at"`
-	UpdatedAt  time.Time           `json:"updated_at"`
-	Status     string              `json:"status"`
-	StatusCode api.StatusCode      `json:"status_code"`
-	Resources  map[string][]string `json:"resources"`
-	Metadata   *Jmap               `json:"metadata"`
-	MayCancel  bool                `json:"may_cancel"`
-	Err        string              `json:"err"`
-}

From b8821ce800aa7b6c253e6c910735491848a5b3be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Dec 2016 18:40:50 -0500
Subject: [PATCH 7/9] shared: Move REST API to new package: profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go             | 14 +++++---------
 lxc/config.go         |  4 ++--
 lxc/profile.go        |  5 +++--
 lxd/db_profiles.go    | 25 ++++++++++++++-----------
 lxd/profiles.go       | 23 ++++++++---------------
 shared/api/profile.go | 33 +++++++++++++++++++++++++++++++++
 shared/container.go   |  8 --------
 7 files changed, 65 insertions(+), 47 deletions(-)
 create mode 100644 shared/api/profile.go

diff --git a/client.go b/client.go
index 3ff0c19..e935ed0 100644
--- a/client.go
+++ b/client.go
@@ -1767,12 +1767,12 @@ func (c *Client) GetLog(container string, log string) (io.Reader, error) {
 	return resp.Body, nil
 }
 
-func (c *Client) ProfileConfig(name string) (*shared.ProfileConfig, error) {
+func (c *Client) ProfileConfig(name string) (*api.Profile, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	ct := shared.ProfileConfig{}
+	ct := api.Profile{}
 
 	resp, err := c.get(fmt.Sprintf("profiles/%s", name))
 	if err != nil {
@@ -2494,20 +2494,16 @@ func (c *Client) SetProfileConfigItem(profile, key, value string) error {
 	return err
 }
 
-func (c *Client) PutProfile(name string, profile shared.ProfileConfig) error {
+func (c *Client) PutProfile(name string, profile api.ProfilePut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	if profile.Name != name {
-		return fmt.Errorf("Cannot change profile name")
-	}
-
 	_, err := c.put(fmt.Sprintf("profiles/%s", name), profile, Sync)
 	return err
 }
 
-func (c *Client) ListProfiles() ([]shared.ProfileConfig, error) {
+func (c *Client) ListProfiles() ([]api.Profile, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2517,7 +2513,7 @@ func (c *Client) ListProfiles() ([]shared.ProfileConfig, error) {
 		return nil, err
 	}
 
-	profiles := []shared.ProfileConfig{}
+	profiles := []api.Profile{}
 	if err := json.Unmarshal(resp.Metadata, &profiles); err != nil {
 		return nil, err
 	}
diff --git a/lxc/config.go b/lxc/config.go
index dc7c558..1a91fa7 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -724,7 +724,7 @@ func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) e
 		dev[key] = value
 		st.Devices[devname] = dev
 
-		err = client.PutProfile(name, *st)
+		err = client.PutProfile(name, st.Writable())
 		if err != nil {
 			return err
 		}
@@ -780,7 +780,7 @@ func (c *configCmd) deviceUnset(config *lxd.Config, which string, args []string)
 		delete(dev, key)
 		st.Devices[devname] = dev
 
-		err = client.PutProfile(name, *st)
+		err = client.PutProfile(name, st.Writable())
 		if err != nil {
 			return err
 		}
diff --git a/lxc/profile.go b/lxc/profile.go
index 5d8a238..41ce1da 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
 )
@@ -179,7 +180,7 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 			return err
 		}
 
-		newdata := shared.ProfileConfig{}
+		newdata := api.ProfilePut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -206,7 +207,7 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.ProfileConfig{}
+		newdata := api.ProfilePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.PutProfile(p, newdata)
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index d935848..abbb3ab 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -7,7 +7,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/lxd/types"
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // dbProfiles returns a string list of profiles.
@@ -29,34 +29,37 @@ func dbProfiles(db *sql.DB) ([]string, error) {
 	return response, nil
 }
 
-func dbProfileGet(db *sql.DB, profile string) (int64, *shared.ProfileConfig, error) {
+func dbProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) {
 	id := int64(-1)
 	description := sql.NullString{}
 
 	q := "SELECT id, description FROM profiles WHERE name=?"
-	arg1 := []interface{}{profile}
+	arg1 := []interface{}{name}
 	arg2 := []interface{}{&id, &description}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	config, err := dbProfileConfig(db, profile)
+	config, err := dbProfileConfig(db, name)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	devices, err := dbDevices(db, profile, true)
+	devices, err := dbDevices(db, name, true)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	return id, &shared.ProfileConfig{
-		Name:        profile,
-		Config:      config,
-		Description: description.String,
-		Devices:     devices,
-	}, nil
+	profile := api.Profile{
+		Name: name,
+	}
+
+	profile.Config = config
+	profile.Description = description.String
+	profile.Devices = devices
+
+	return id, &profile, nil
 }
 
 func dbProfileCreate(db *sql.DB, profile string, description string, config map[string]string,
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 98e852c..181d0ca 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -12,21 +12,14 @@ import (
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 /* This is used for both profiles post and profile put */
-type profilesPostReq struct {
-	Name        string            `json:"name"`
-	Config      map[string]string `json:"config"`
-	Description string            `json:"description"`
-	Devices     types.Devices     `json:"devices"`
-}
-
 func profilesGet(d *Daemon, r *http.Request) Response {
 	results, err := dbProfiles(d.db)
 	if err != nil {
@@ -36,7 +29,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 	recursion := d.isRecursionRequest(r)
 
 	resultString := make([]string, len(results))
-	resultMap := make([]*shared.ProfileConfig, len(results))
+	resultMap := make([]*api.Profile, len(results))
 	i := 0
 	for _, name := range results {
 		if !recursion {
@@ -61,7 +54,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 }
 
 func profilesPost(d *Daemon, r *http.Request) Response {
-	req := profilesPostReq{}
+	req := api.ProfilesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -109,7 +102,7 @@ var profilesCmd = Command{
 	get:  profilesGet,
 	post: profilesPost}
 
-func doProfileGet(d *Daemon, name string) (*shared.ProfileConfig, error) {
+func doProfileGet(d *Daemon, name string) (*api.Profile, error) {
 	_, profile, err := dbProfileGet(d.db, name)
 	if err != nil {
 		return nil, err
@@ -174,7 +167,7 @@ func profilePut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	req := profilesPostReq{}
+	req := api.ProfilePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -209,7 +202,7 @@ func profilePatch(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	req := profilesPostReq{}
+	req := api.ProfilePut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -247,7 +240,7 @@ func profilePatch(d *Daemon, r *http.Request) Response {
 	return doProfileUpdate(d, name, id, profile, req)
 }
 
-func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileConfig, req profilesPostReq) Response {
+func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req api.ProfilePut) Response {
 	// Sanity checks
 	err := containerValidConfig(d, req.Config, true, false)
 	if err != nil {
@@ -339,7 +332,7 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileCo
 func profilePost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	req := profilesPostReq{}
+	req := api.ProfilePost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/shared/api/profile.go b/shared/api/profile.go
new file mode 100644
index 0000000..861d08f
--- /dev/null
+++ b/shared/api/profile.go
@@ -0,0 +1,33 @@
+package api
+
+// ProfilesPost represents the fields of a new LXD profile
+type ProfilesPost struct {
+	ProfilePut
+
+	Name string `json:"name"`
+}
+
+// ProfilePost represents the fields required to rename a LXD profile
+type ProfilePost struct {
+	Name string `json:"name"`
+}
+
+// ProfilePut represents the modifiable fields of a LXD profile
+type ProfilePut struct {
+	Config      map[string]string            `json:"config"`
+	Description string                       `json:"description"`
+	Devices     map[string]map[string]string `json:"devices"`
+}
+
+// Profile represents a LXD profile
+type Profile struct {
+	ProfilePut
+
+	Name   string   `json:"name"`
+	UsedBy []string `json:"used_by"`
+}
+
+// Convert a full Profile struct into a ProfilePut struct (filters read-only fields)
+func (profile *Profile) Writable() ProfilePut {
+	return profile.ProfilePut
+}
diff --git a/shared/container.go b/shared/container.go
index 5d0052b..ded95c4 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -147,14 +147,6 @@ const (
 	Unfreeze ContainerAction = "unfreeze"
 )
 
-type ProfileConfig struct {
-	Name        string                       `json:"name"`
-	Config      map[string]string            `json:"config"`
-	Description string                       `json:"description"`
-	Devices     map[string]map[string]string `json:"devices"`
-	UsedBy      []string                     `json:"used_by"`
-}
-
 func IsInt64(value string) error {
 	if value == "" {
 		return nil

From 599453f4efddd533d6667a683789839a8f6d2479 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Dec 2016 15:16:30 -0500
Subject: [PATCH 8/9] shared: Move REST API to new package: container
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                        |  22 +++----
 lxc/config.go                    |  21 ++++---
 lxc/exec.go                      |   7 ++-
 lxc/info.go                      |   4 +-
 lxc/list.go                      |  54 ++++++++--------
 lxc/list_test.go                 |   3 +-
 lxc/profile.go                   |   4 +-
 lxc/publish.go                   |   4 +-
 lxd/api_internal.go              |   4 +-
 lxd/container.go                 |   3 +-
 lxd/container_exec.go            |  17 ++---
 lxd/container_lxc.go             |  57 +++++++++--------
 lxd/container_patch.go           |   3 +-
 lxd/container_post.go            |   9 +--
 lxd/container_put.go             |  13 +---
 lxd/container_snapshot.go        |  14 ++---
 lxd/container_state.go           |  10 +--
 lxd/container_test.go            |   3 +-
 lxd/containers_get.go            |   8 +--
 lxd/containers_post.go           |  52 ++--------------
 lxd/main_forkgetnet.go           |  12 ++--
 shared/api/container.go          |  86 +++++++++++++++++++++++++
 shared/api/container_exec.go     |  19 ++++++
 shared/api/container_snapshot.go |  32 ++++++++++
 shared/api/container_state.go    |  66 ++++++++++++++++++++
 shared/container.go              | 131 ---------------------------------------
 test/lxd-benchmark/main.go       |   5 +-
 27 files changed, 337 insertions(+), 326 deletions(-)
 create mode 100644 shared/api/container.go
 create mode 100644 shared/api/container_exec.go
 create mode 100644 shared/api/container_snapshot.go
 create mode 100644 shared/api/container_state.go

diff --git a/client.go b/client.go
index e935ed0..1635e50 100644
--- a/client.go
+++ b/client.go
@@ -609,7 +609,7 @@ func (c *Client) IsPublic() bool {
 	return public
 }
 
-func (c *Client) ListContainers() ([]shared.ContainerInfo, error) {
+func (c *Client) ListContainers() ([]api.Container, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -619,7 +619,7 @@ func (c *Client) ListContainers() ([]shared.ContainerInfo, error) {
 		return nil, err
 	}
 
-	var result []shared.ContainerInfo
+	var result []api.Container
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -1715,12 +1715,12 @@ func (c *Client) ServerStatus() (*api.Server, error) {
 	return &ss, nil
 }
 
-func (c *Client) ContainerInfo(name string) (*shared.ContainerInfo, error) {
+func (c *Client) ContainerInfo(name string) (*api.Container, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	ct := shared.ContainerInfo{}
+	ct := api.Container{}
 
 	resp, err := c.get(fmt.Sprintf("containers/%s", name))
 	if err != nil {
@@ -1734,12 +1734,12 @@ func (c *Client) ContainerInfo(name string) (*shared.ContainerInfo, error) {
 	return &ct, nil
 }
 
-func (c *Client) ContainerState(name string) (*shared.ContainerState, error) {
+func (c *Client) ContainerState(name string) (*api.ContainerState, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	ct := shared.ContainerState{}
+	ct := api.ContainerState{}
 
 	resp, err := c.get(fmt.Sprintf("containers/%s/state", name))
 	if err != nil {
@@ -2280,7 +2280,7 @@ func (c *Client) Snapshot(container string, snapshotName string, stateful bool)
 	return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, Async)
 }
 
-func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error) {
+func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2291,7 +2291,7 @@ func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error)
 		return nil, err
 	}
 
-	var result []shared.SnapshotInfo
+	var result []api.ContainerSnapshot
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -2300,7 +2300,7 @@ func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error)
 	return result, nil
 }
 
-func (c *Client) SnapshotInfo(snapName string) (*shared.SnapshotInfo, error) {
+func (c *Client) SnapshotInfo(snapName string) (*api.ContainerSnapshot, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2316,7 +2316,7 @@ func (c *Client) SnapshotInfo(snapName string) (*shared.SnapshotInfo, error) {
 		return nil, err
 	}
 
-	var result shared.SnapshotInfo
+	var result api.ContainerSnapshot
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -2427,7 +2427,7 @@ func (c *Client) SetContainerConfig(container, key, value string) error {
 	return c.WaitForSuccess(resp.Operation)
 }
 
-func (c *Client) UpdateContainerConfig(container string, st shared.BriefContainerInfo) error {
+func (c *Client) UpdateContainerConfig(container string, st api.ContainerPut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/config.go b/lxc/config.go
index 1a91fa7..0f2ad48 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -345,21 +345,21 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			brief := config.Writable()
 			data, err = yaml.Marshal(&brief)
 		} else {
-			var brief shared.BriefContainerInfo
+			var brief api.ContainerPut
 			if shared.IsSnapshot(container) {
 				config, err := d.SnapshotInfo(container)
 				if err != nil {
 					return err
 				}
 
-				brief = shared.BriefContainerInfo{
+				brief = api.ContainerPut{
 					Profiles:  config.Profiles,
 					Config:    config.Config,
 					Devices:   config.Devices,
 					Ephemeral: config.Ephemeral,
 				}
 				if c.expanded {
-					brief = shared.BriefContainerInfo{
+					brief = api.ContainerPut{
 						Profiles:  config.Profiles,
 						Config:    config.ExpandedConfig,
 						Devices:   config.ExpandedDevices,
@@ -372,9 +372,10 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 					return err
 				}
 
-				brief = config.Brief()
+				brief = config.Writable()
 				if c.expanded {
-					brief = config.BriefExpanded()
+					brief.Config = config.ExpandedConfig
+					brief.Devices = config.ExpandedDevices
 				}
 			}
 
@@ -492,7 +493,7 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 			return err
 		}
 
-		newdata := shared.BriefContainerInfo{}
+		newdata := api.ContainerPut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -506,7 +507,7 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 		return err
 	}
 
-	brief := config.Brief()
+	brief := config.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -520,7 +521,7 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.BriefContainerInfo{}
+		newdata := api.ContainerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.UpdateContainerConfig(cont, newdata)
@@ -742,7 +743,7 @@ func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) e
 		dev[key] = value
 		st.Devices[devname] = dev
 
-		err = client.UpdateContainerConfig(name, st.Brief())
+		err = client.UpdateContainerConfig(name, st.Writable())
 		if err != nil {
 			return err
 		}
@@ -798,7 +799,7 @@ func (c *configCmd) deviceUnset(config *lxd.Config, which string, args []string)
 		delete(dev, key)
 		st.Devices[devname] = dev
 
-		err = client.UpdateContainerConfig(name, st.Brief())
+		err = client.UpdateContainerConfig(name, st.Writable())
 		if err != nil {
 			return err
 		}
diff --git a/lxc/exec.go b/lxc/exec.go
index ef278bd..2d2645c 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
@@ -68,7 +69,7 @@ func (c *execCmd) sendTermSize(control *websocket.Conn) error {
 		return err
 	}
 
-	msg := shared.ContainerExecControl{}
+	msg := api.ContainerExecControl{}
 	msg.Command = "window-resize"
 	msg.Args = make(map[string]string)
 	msg.Args["width"] = strconv.Itoa(width)
@@ -92,9 +93,9 @@ func (c *execCmd) forwardSignal(control *websocket.Conn, sig syscall.Signal) err
 		return err
 	}
 
-	msg := shared.ContainerExecControl{}
+	msg := api.ContainerExecControl{}
 	msg.Command = "signal"
-	msg.Signal = sig
+	msg.Signal = int(sig)
 
 	buf, err := json.Marshal(msg)
 	if err != nil {
diff --git a/lxc/info.go b/lxc/info.go
index a1f6e9f..30a3f66 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -91,8 +91,8 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		fmt.Printf(i18n.G("Remote: %s")+"\n", d.Remote.Addr)
 	}
 	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
-	if ct.CreationDate.UTC().Unix() != 0 {
-		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreationDate.UTC().Format(layout))
+	if ct.CreatedAt.UTC().Unix() != 0 {
+		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout))
 	}
 
 	fmt.Printf(i18n.G("Status: %s")+"\n", ct.Status)
diff --git a/lxc/list.go b/lxc/list.go
index 3d4f75a..8311e90 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -25,7 +26,7 @@ type column struct {
 	NeedsSnapshots bool
 }
 
-type columnData func(shared.ContainerInfo, *shared.ContainerState, []shared.SnapshotInfo) string
+type columnData func(api.Container, *api.ContainerState, []api.ContainerSnapshot) string
 
 type byName [][]string
 
@@ -142,7 +143,7 @@ func (c *listCmd) dotPrefixMatch(short string, full string) bool {
 	return true
 }
 
-func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool {
+func (c *listCmd) shouldShow(filters []string, state *api.Container) bool {
 	for _, filter := range filters {
 		if strings.Contains(filter, "=") {
 			membs := strings.SplitN(filter, "=", 2)
@@ -207,7 +208,7 @@ func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool
 	return true
 }
 
-func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, filters []string, columns []column) error {
+func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters []string, columns []column) error {
 	headers := []string{}
 	for _, column := range columns {
 		headers = append(headers, column.Name)
@@ -218,12 +219,12 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 		threads = len(cinfos)
 	}
 
-	cStates := map[string]*shared.ContainerState{}
+	cStates := map[string]*api.ContainerState{}
 	cStatesLock := sync.Mutex{}
 	cStatesQueue := make(chan string, threads)
 	cStatesWg := sync.WaitGroup{}
 
-	cSnapshots := map[string][]shared.SnapshotInfo{}
+	cSnapshots := map[string][]api.ContainerSnapshot{}
 	cSnapshotsLock := sync.Mutex{}
 	cSnapshotsQueue := make(chan string, threads)
 	cSnapshotsWg := sync.WaitGroup{}
@@ -347,7 +348,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 	case listFormatJSON:
 		data := make([]listContainerItem, len(cinfos))
 		for i := range cinfos {
-			data[i].ContainerInfo = &cinfos[i]
+			data[i].Container = &cinfos[i]
 			data[i].State = cStates[cinfos[i].Name]
 			data[i].Snapshots = cSnapshots[cinfos[i].Name]
 		}
@@ -364,9 +365,10 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 }
 
 type listContainerItem struct {
-	*shared.ContainerInfo
-	State     *shared.ContainerState `json:"state"`
-	Snapshots []shared.SnapshotInfo  `json:"snapshots"`
+	*api.Container
+
+	State     *api.ContainerState
+	Snapshots []api.ContainerSnapshot
 }
 
 func (c *listCmd) run(config *lxd.Config, args []string) error {
@@ -396,7 +398,7 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	var cts []shared.ContainerInfo
+	var cts []api.Container
 	ctslist, err := d.ListContainers()
 	if err != nil {
 		return err
@@ -490,7 +492,7 @@ func (c *listCmd) parseColumns() ([]column, error) {
 				}
 			}
 
-			column.Data = func(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+			column.Data = func(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 				v, ok := cInfo.Config[k]
 				if !ok {
 					v, ok = cInfo.ExpandedConfig[k]
@@ -509,15 +511,15 @@ func (c *listCmd) parseColumns() ([]column, error) {
 	return columns, nil
 }
 
-func (c *listCmd) nameColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) nameColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return cInfo.Name
 }
 
-func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) statusColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return strings.ToUpper(cInfo.Status)
 }
 
-func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) IP4ColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.IsActive() && cState != nil && cState.Network != nil {
 		ipv4s := []string{}
 		for netName, net := range cState.Network {
@@ -541,7 +543,7 @@ func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 	}
 }
 
-func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) IP6ColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.IsActive() && cState != nil && cState.Network != nil {
 		ipv6s := []string{}
 		for netName, net := range cState.Network {
@@ -565,7 +567,7 @@ func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 	}
 }
 
-func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) typeColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.Ephemeral {
 		return i18n.G("EPHEMERAL")
 	} else {
@@ -573,7 +575,7 @@ func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.Cont
 	}
 }
 
-func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) numberSnapshotsColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cSnaps != nil {
 		return fmt.Sprintf("%d", len(cSnaps))
 	}
@@ -581,7 +583,7 @@ func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *
 	return ""
 }
 
-func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) PIDColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.IsActive() && cState != nil {
 		return fmt.Sprintf("%d", cState.Pid)
 	}
@@ -589,29 +591,29 @@ func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 	return ""
 }
 
-func (c *listCmd) ArchitectureColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) ArchitectureColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return cInfo.Architecture
 }
 
-func (c *listCmd) ProfilesColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) ProfilesColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return strings.Join(cInfo.Profiles, "\n")
 }
 
-func (c *listCmd) CreatedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) CreatedColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	layout := "2006/01/02 15:04 UTC"
 
-	if cInfo.CreationDate.UTC().Unix() != 0 {
-		return cInfo.CreationDate.UTC().Format(layout)
+	if cInfo.CreatedAt.UTC().Unix() != 0 {
+		return cInfo.CreatedAt.UTC().Format(layout)
 	}
 
 	return ""
 }
 
-func (c *listCmd) LastUsedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) LastUsedColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	layout := "2006/01/02 15:04 UTC"
 
-	if !cInfo.LastUsedDate.IsZero() && cInfo.LastUsedDate.UTC().Unix() != 0 {
-		return cInfo.LastUsedDate.UTC().Format(layout)
+	if !cInfo.LastUsedAt.IsZero() && cInfo.LastUsedAt.UTC().Unix() != 0 {
+		return cInfo.LastUsedAt.UTC().Format(layout)
 	}
 
 	return ""
diff --git a/lxc/list_test.go b/lxc/list_test.go
index ae4fe0f..b92f348 100644
--- a/lxc/list_test.go
+++ b/lxc/list_test.go
@@ -8,6 +8,7 @@ import (
 	"testing"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func TestDotPrefixMatch(t *testing.T) {
@@ -25,7 +26,7 @@ func TestDotPrefixMatch(t *testing.T) {
 func TestShouldShow(t *testing.T) {
 	list := listCmd{}
 
-	state := &shared.ContainerInfo{
+	state := &api.Container{
 		Name: "foo",
 		ExpandedConfig: map[string]string{
 			"security.privileged": "1",
diff --git a/lxc/profile.go b/lxc/profile.go
index 41ce1da..30cc3c8 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -267,7 +267,7 @@ func (c *profileCmd) doProfileAdd(client *lxd.Client, d string, p string) error
 
 	ct.Profiles = append(ct.Profiles, p)
 
-	err = client.UpdateContainerConfig(d, ct.Brief())
+	err = client.UpdateContainerConfig(d, ct.Writable())
 	if err != nil {
 		return err
 	}
@@ -298,7 +298,7 @@ func (c *profileCmd) doProfileRemove(client *lxd.Client, d string, p string) err
 
 	ct.Profiles = profiles
 
-	err = client.UpdateContainerConfig(d, ct.Brief())
+	err = client.UpdateContainerConfig(d, ct.Writable())
 	if err != nil {
 		return err
 	}
diff --git a/lxc/publish.go b/lxc/publish.go
index 38c6bd6..de53f61 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -94,7 +94,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 
 			if ct.Ephemeral {
 				ct.Ephemeral = false
-				err := s.UpdateContainerConfig(cName, ct.Brief())
+				err := s.UpdateContainerConfig(cName, ct.Writable())
 				if err != nil {
 					return err
 				}
@@ -117,7 +117,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 
 			if wasEphemeral {
 				ct.Ephemeral = true
-				err := s.UpdateContainerConfig(cName, ct.Brief())
+				err := s.UpdateContainerConfig(cName, ct.Writable())
 				if err != nil {
 					return err
 				}
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 7fa2205..0fc97f4 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -150,8 +150,8 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		Architecture: arch,
 		BaseImage:    baseImage,
 		Config:       sf.Container.Config,
-		CreationDate: sf.Container.CreationDate,
-		LastUsedDate: sf.Container.LastUsedDate,
+		CreationDate: sf.Container.CreatedAt,
+		LastUsedDate: sf.Container.LastUsedAt,
 		Ctype:        cTypeRegular,
 		Devices:      sf.Container.Devices,
 		Ephemeral:    sf.Container.Ephemeral,
diff --git a/lxd/container.go b/lxd/container.go
index 2b916ea..1a7556a 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -374,7 +375,7 @@ type container interface {
 
 	// Status
 	Render() (interface{}, interface{}, error)
-	RenderState() (*shared.ContainerState, error)
+	RenderState() (*api.ContainerState, error)
 	IsPrivileged() bool
 	IsRunning() bool
 	IsFrozen() bool
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 6161dec..60c15f2 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -16,21 +16,12 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type commandPostContent struct {
-	Command      []string          `json:"command"`
-	WaitForWS    bool              `json:"wait-for-websocket"`
-	RecordOutput bool              `json:"record-output"`
-	Interactive  bool              `json:"interactive"`
-	Environment  map[string]string `json:"environment"`
-	Width        int               `json:"width"`
-	Height       int               `json:"height"`
-}
-
 type execWs struct {
 	command   []string
 	container container
@@ -174,7 +165,7 @@ func (s *execWs) Do(op *operation) error {
 					break
 				}
 
-				command := shared.ContainerExecControl{}
+				command := api.ContainerExecControl{}
 
 				if err := json.Unmarshal(buf, &command); err != nil {
 					shared.LogDebugf("Failed to unmarshal control socket command: %s", err)
@@ -200,7 +191,7 @@ func (s *execWs) Do(op *operation) error {
 						continue
 					}
 				} else if command.Command == "signal" {
-					if err := syscall.Kill(attachedChildPid, command.Signal); err != nil {
+					if err := syscall.Kill(attachedChildPid, syscall.Signal(command.Signal)); err != nil {
 						shared.LogDebugf("Failed forwarding signal '%s' to PID %d.", command.Signal, attachedChildPid)
 						continue
 					}
@@ -318,7 +309,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Container is frozen."))
 	}
 
-	post := commandPostContent{}
+	post := api.ContainerExecPost{}
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		return BadRequest(err)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f4b1221..fb9f1c8 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2403,7 +2403,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
 	etag := []interface{}{c.architecture, c.localConfig, c.localDevices, c.ephemeral, c.profiles}
 
 	if c.IsSnapshot() {
-		return &shared.SnapshotInfo{
+		return &api.ContainerSnapshot{
 			Architecture:    architectureName,
 			Config:          c.localConfig,
 			CreationDate:    c.creationDate,
@@ -2424,25 +2424,28 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
 		}
 		statusCode := lxcStatusCode(cState)
 
-		return &shared.ContainerInfo{
-			Architecture:    architectureName,
-			Config:          c.localConfig,
-			CreationDate:    c.creationDate,
-			Devices:         c.localDevices,
-			Ephemeral:       c.ephemeral,
+		ct := api.Container{
 			ExpandedConfig:  c.expandedConfig,
 			ExpandedDevices: c.expandedDevices,
-			LastUsedDate:    c.lastUsedDate,
 			Name:            c.name,
-			Profiles:        c.profiles,
 			Status:          statusCode.String(),
 			StatusCode:      statusCode,
 			Stateful:        c.stateful,
-		}, etag, nil
+		}
+
+		ct.Architecture = architectureName
+		ct.Config = c.localConfig
+		ct.CreatedAt = c.creationDate
+		ct.Devices = c.localDevices
+		ct.Ephemeral = c.ephemeral
+		ct.LastUsedAt = c.lastUsedDate
+		ct.Profiles = c.profiles
+
+		return &ct, etag, nil
 	}
 }
 
-func (c *containerLXC) RenderState() (*shared.ContainerState, error) {
+func (c *containerLXC) RenderState() (*api.ContainerState, error) {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
@@ -2454,7 +2457,7 @@ func (c *containerLXC) RenderState() (*shared.ContainerState, error) {
 		return nil, err
 	}
 	statusCode := lxcStatusCode(cState)
-	status := shared.ContainerState{
+	status := api.ContainerState{
 		Status:     statusCode.String(),
 		StatusCode: statusCode,
 	}
@@ -2786,8 +2789,8 @@ func (c *containerLXC) ConfigKeySet(key string, value string) error {
 }
 
 type backupFile struct {
-	Container *shared.ContainerInfo  `yaml:"container"`
-	Snapshots []*shared.SnapshotInfo `yaml:"snapshots"`
+	Container *api.Container           `yaml:"container"`
+	Snapshots []*api.ContainerSnapshot `yaml:"snapshots"`
 }
 
 func writeBackupFile(c container) error {
@@ -2817,7 +2820,7 @@ func writeBackupFile(c container) error {
 		return err
 	}
 
-	var sis []*shared.SnapshotInfo
+	var sis []*api.ContainerSnapshot
 
 	for _, s := range snapshots {
 		si, _, err := s.Render()
@@ -2825,11 +2828,11 @@ func writeBackupFile(c container) error {
 			return err
 		}
 
-		sis = append(sis, si.(*shared.SnapshotInfo))
+		sis = append(sis, si.(*api.ContainerSnapshot))
 	}
 
 	data, err := yaml.Marshal(&backupFile{
-		Container: ci.(*shared.ContainerInfo),
+		Container: ci.(*api.Container),
 		Snapshots: sis,
 	})
 	if err != nil {
@@ -4560,8 +4563,8 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	return 0, attachedPid, nil
 }
 
-func (c *containerLXC) cpuState() shared.ContainerStateCPU {
-	cpu := shared.ContainerStateCPU{}
+func (c *containerLXC) cpuState() api.ContainerStateCPU {
+	cpu := api.ContainerStateCPU{}
 
 	if !cgCpuacctController {
 		return cpu
@@ -4579,8 +4582,8 @@ func (c *containerLXC) cpuState() shared.ContainerStateCPU {
 	return cpu
 }
 
-func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
-	disk := map[string]shared.ContainerStateDisk{}
+func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {
+	disk := map[string]api.ContainerStateDisk{}
 
 	for _, name := range c.expandedDevices.DeviceNames() {
 		d := c.expandedDevices[name]
@@ -4597,14 +4600,14 @@ func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
 			continue
 		}
 
-		disk[name] = shared.ContainerStateDisk{Usage: usage}
+		disk[name] = api.ContainerStateDisk{Usage: usage}
 	}
 
 	return disk
 }
 
-func (c *containerLXC) memoryState() shared.ContainerStateMemory {
-	memory := shared.ContainerStateMemory{}
+func (c *containerLXC) memoryState() api.ContainerStateMemory {
+	memory := api.ContainerStateMemory{}
 
 	if !cgMemoryController {
 		return memory
@@ -4650,8 +4653,8 @@ func (c *containerLXC) memoryState() shared.ContainerStateMemory {
 	return memory
 }
 
-func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
-	result := map[string]shared.ContainerStateNetwork{}
+func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
+	result := map[string]api.ContainerStateNetwork{}
 
 	pid := c.InitPID()
 	if pid < 1 {
@@ -4670,7 +4673,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 		return result
 	}
 
-	networks := map[string]shared.ContainerStateNetwork{}
+	networks := map[string]api.ContainerStateNetwork{}
 
 	err = json.Unmarshal(out, &networks)
 	if err != nil {
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index f7752bb..f650c91 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -41,7 +42,7 @@ func containerPatch(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	req := containerPutReq{}
+	req := api.ContainerPut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_post.go b/lxd/container_post.go
index 4139dd7..066a2a3 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -6,12 +6,9 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
-)
 
-type containerPostBody struct {
-	Migration bool   `json:"migration"`
-	Name      string `json:"name"`
-}
+	"github.com/lxc/lxd/shared/api"
+)
 
 func containerPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
@@ -25,7 +22,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	body := containerPostBody{}
+	body := api.ContainerPost{}
 	if err := json.Unmarshal(buf, &body); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 15407df..135a41b 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -8,22 +8,13 @@ import (
 
 	"github.com/gorilla/mux"
 
-	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type containerPutReq struct {
-	Architecture string            `json:"architecture"`
-	Config       map[string]string `json:"config"`
-	Devices      types.Devices     `json:"devices"`
-	Ephemeral    bool              `json:"ephemeral"`
-	Profiles     []string          `json:"profiles"`
-	Restore      string            `json:"restore"`
-}
-
 /*
  * Update configuration, or, if 'restore:snapshot-name' is present, restore
  * the named snapshot
@@ -43,7 +34,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	configRaw := containerPutReq{}
+	configRaw := api.ContainerPut{}
 	if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index bc75729..473e2a2 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -10,14 +10,10 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
-type containerSnapshotPostReq struct {
-	Name     string `json:"name"`
-	Stateful bool   `json:"stateful"`
-}
-
 func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	recursionStr := r.FormValue("recursion")
 	recursion, err := strconv.Atoi(recursionStr)
@@ -37,7 +33,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	resultString := []string{}
-	resultMap := []*shared.SnapshotInfo{}
+	resultMap := []*api.ContainerSnapshot{}
 
 	for _, snap := range snaps {
 		snapName := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)[1]
@@ -50,7 +46,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 				continue
 			}
 
-			resultMap = append(resultMap, render.(*shared.SnapshotInfo))
+			resultMap = append(resultMap, render.(*api.ContainerSnapshot))
 		}
 	}
 
@@ -111,7 +107,7 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	req := containerSnapshotPostReq{}
+	req := api.ContainerSnapshotsPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -189,7 +185,7 @@ func snapshotGet(sc container, name string) Response {
 		return SmartError(err)
 	}
 
-	return SyncResponse(true, render.(*shared.SnapshotInfo))
+	return SyncResponse(true, render.(*api.ContainerSnapshot))
 }
 
 func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
diff --git a/lxd/container_state.go b/lxd/container_state.go
index 1a4ca8c..3115f58 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -9,15 +9,9 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
-type containerStatePutReq struct {
-	Action   string `json:"action"`
-	Timeout  int    `json:"timeout"`
-	Force    bool   `json:"force"`
-	Stateful bool   `json:"stateful"`
-}
-
 func containerState(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
@@ -36,7 +30,7 @@ func containerState(d *Daemon, r *http.Request) Response {
 func containerStatePut(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	raw := containerStatePutReq{}
+	raw := api.ContainerStatePut{}
 
 	// We default to -1 (i.e. no timeout) here instead of 0 (instant
 	// timeout).
diff --git a/lxd/container_test.go b/lxd/container_test.go
index da83174..e389cf9 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func (suite *lxdTestSuite) TestContainer_ProfilesDefault() {
@@ -87,7 +88,7 @@ func (suite *lxdTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 	out, _, err := c.Render()
 	suite.Req.Nil(err)
 
-	state := out.(*shared.ContainerInfo)
+	state := out.(*api.Container)
 	defer c.Delete()
 
 	suite.Equal(
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index d3719ec..2a10110 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -37,7 +37,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 	}
 
 	resultString := []string{}
-	resultList := []*shared.ContainerInfo{}
+	resultList := []*api.Container{}
 	if err != nil {
 		return []string{}, err
 	}
@@ -49,7 +49,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 		} else {
 			c, err := doContainerGet(d, container)
 			if err != nil {
-				c = &shared.ContainerInfo{
+				c = &api.Container{
 					Name:       container,
 					Status:     api.Error.String(),
 					StatusCode: api.Error}
@@ -65,7 +65,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 	return resultList, nil
 }
 
-func doContainerGet(d *Daemon, cname string) (*shared.ContainerInfo, error) {
+func doContainerGet(d *Daemon, cname string) (*api.Container, error) {
 	c, err := containerLoadByName(d, cname)
 	if err != nil {
 		return nil, err
@@ -76,5 +76,5 @@ func doContainerGet(d *Daemon, cname string) (*shared.ContainerInfo, error) {
 		return nil, err
 	}
 
-	return cts.(*shared.ContainerInfo), nil
+	return cts.(*api.Container), nil
 }
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 306c590..fd9babc 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -19,49 +19,7 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type containerImageSource struct {
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
-
-	/* for "image" type */
-	Alias       string            `json:"alias"`
-	Fingerprint string            `json:"fingerprint"`
-	Properties  map[string]string `json:"properties"`
-	Server      string            `json:"server"`
-	Secret      string            `json:"secret"`
-	Protocol    string            `json:"protocol"`
-
-	/*
-	 * for "migration" and "copy" types, as an optimization users can
-	 * provide an image hash to extract before the filesystem is rsync'd,
-	 * potentially cutting down filesystem transfer time. LXD will not go
-	 * and fetch this image, it will simply use it if it exists in the
-	 * image store.
-	 */
-	BaseImage string `json:"base-image"`
-
-	/* for "migration" type */
-	Mode       string            `json:"mode"`
-	Operation  string            `json:"operation"`
-	Websockets map[string]string `json:"secrets"`
-
-	/* for "copy" type */
-	Source string `json:"source"`
-	/* for "migration" type. Whether the migration is live. */
-	Live bool `json:"live"`
-}
-
-type containerPostReq struct {
-	Architecture string               `json:"architecture"`
-	Config       map[string]string    `json:"config"`
-	Devices      types.Devices        `json:"devices"`
-	Ephemeral    bool                 `json:"ephemeral"`
-	Name         string               `json:"name"`
-	Profiles     []string             `json:"profiles"`
-	Source       containerImageSource `json:"source"`
-}
-
-func createFromImage(d *Daemon, req *containerPostReq) Response {
+func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 	var hash string
 	var err error
 
@@ -174,7 +132,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 	return OperationResponse(op)
 }
 
-func createFromNone(d *Daemon, req *containerPostReq) Response {
+func createFromNone(d *Daemon, req *api.ContainersPost) Response {
 	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
 		architecture = 0
@@ -206,7 +164,7 @@ func createFromNone(d *Daemon, req *containerPostReq) Response {
 	return OperationResponse(op)
 }
 
-func createFromMigration(d *Daemon, req *containerPostReq) Response {
+func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 	if req.Source.Mode != "pull" && req.Source.Mode != "push" {
 		return NotImplemented
 	}
@@ -335,7 +293,7 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 	return OperationResponse(op)
 }
 
-func createFromCopy(d *Daemon, req *containerPostReq) Response {
+func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 	if req.Source.Source == "" {
 		return BadRequest(fmt.Errorf("must specify a source container"))
 	}
@@ -406,7 +364,7 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 func containersPost(d *Daemon, r *http.Request) Response {
 	shared.LogDebugf("Responding to container create")
 
-	req := containerPostReq{}
+	req := api.ContainersPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/main_forkgetnet.go b/lxd/main_forkgetnet.go
index 7e016f3..5ca9fdb 100644
--- a/lxd/main_forkgetnet.go
+++ b/lxd/main_forkgetnet.go
@@ -8,11 +8,11 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdForkGetNet() error {
-	networks := map[string]shared.ContainerStateNetwork{}
+	networks := map[string]api.ContainerStateNetwork{}
 
 	interfaces, err := net.Interfaces()
 	if err != nil {
@@ -75,9 +75,9 @@ func cmdForkGetNet() error {
 			netState = "up"
 		}
 
-		network := shared.ContainerStateNetwork{
-			Addresses: []shared.ContainerStateNetworkAddress{},
-			Counters:  shared.ContainerStateNetworkCounters{},
+		network := api.ContainerStateNetwork{
+			Addresses: []api.ContainerStateNetworkAddress{},
+			Counters:  api.ContainerStateNetworkCounters{},
 			Hwaddr:    netIf.HardwareAddr.String(),
 			Mtu:       netIf.MTU,
 			State:     netState,
@@ -114,7 +114,7 @@ func cmdForkGetNet() error {
 					scope = "link"
 				}
 
-				address := shared.ContainerStateNetworkAddress{}
+				address := api.ContainerStateNetworkAddress{}
 				address.Family = family
 				address.Address = fields[0]
 				address.Netmask = fields[1]
diff --git a/shared/api/container.go b/shared/api/container.go
new file mode 100644
index 0000000..2bb3437
--- /dev/null
+++ b/shared/api/container.go
@@ -0,0 +1,86 @@
+package api
+
+import (
+	"time"
+)
+
+// ContainersPost represents the fields available for a new LXD container
+type ContainersPost struct {
+	ContainerPut
+
+	Name   string          `json:"name"`
+	Source ContainerSource `json:"source"`
+}
+
+// ContainerPost represents the fields required to rename/move a LXD container
+type ContainerPost struct {
+	Migration bool   `json:"migration"`
+	Name      string `json:"name"`
+}
+
+// ContainerPut represents the modifiable fields of a LXD container
+type ContainerPut struct {
+	Architecture string                       `json:"architecture"`
+	Config       map[string]string            `json:"config"`
+	Devices      map[string]map[string]string `json:"devices"`
+	Ephemeral    bool                         `json:"ephemeral"`
+	Profiles     []string                     `json:"profiles"`
+	Restore      string                       `json:"restore,omitempty"`
+}
+
+// Container represents a LXD container
+type Container struct {
+	ContainerPut
+
+	CreatedAt       time.Time                    `json:"created_at"`
+	ExpandedConfig  map[string]string            `json:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
+	LastUsedAt      time.Time                    `json:"last_used_at"`
+	Name            string                       `json:"name"`
+	Stateful        bool                         `json:"stateful"`
+	Status          string                       `json:"status"`
+	StatusCode      StatusCode                   `json:"status_code"`
+}
+
+// Convert a full Container struct into a ContainerPut struct (filters read-only fields)
+func (c *Container) Writable() ContainerPut {
+	return c.ContainerPut
+}
+
+// Check whether the container state indicates the container is active
+func (c Container) IsActive() bool {
+	switch c.StatusCode {
+	case Stopped:
+		return false
+	case Error:
+		return false
+	default:
+		return true
+	}
+}
+
+// ContainerSource represents the creation source for a new container
+type ContainerSource struct {
+	Type        string `json:"type"`
+	Certificate string `json:"certificate"`
+
+	/* For "image" type */
+	Alias       string            `json:"alias,omitempty"`
+	Fingerprint string            `json:"fingerprint,omitempty"`
+	Properties  map[string]string `json:"properties,omitempty"`
+	Server      string            `json:"server,omitempty"`
+	Secret      string            `json:"secret,omitempty"`
+	Protocol    string            `json:"protocol,omitempty"`
+
+	/* For "migration" and "copy" types */
+	BaseImage string `json:"base-image,omitempty"`
+
+	/* For "migration" type */
+	Mode       string            `json:"mode,omitempty"`
+	Operation  string            `json:"operation,omitempty"`
+	Websockets map[string]string `json:"secrets,omitempty"`
+	Live       bool              `json:"live,omitempty"`
+
+	/* For "copy" type */
+	Source string `json:"source,omitempty"`
+}
diff --git a/shared/api/container_exec.go b/shared/api/container_exec.go
new file mode 100644
index 0000000..ab84972
--- /dev/null
+++ b/shared/api/container_exec.go
@@ -0,0 +1,19 @@
+package api
+
+// ContainerExecControl represents a message on the container exec "control" socket
+type ContainerExecControl struct {
+	Command string            `json:"command"`
+	Args    map[string]string `json:"args"`
+	Signal  int               `json:"signal"`
+}
+
+// ContainerExecPost represents a LXD container exec request
+type ContainerExecPost struct {
+	Command      []string          `json:"command"`
+	WaitForWS    bool              `json:"wait-for-websocket"`
+	RecordOutput bool              `json:"record-output"`
+	Interactive  bool              `json:"interactive"`
+	Environment  map[string]string `json:"environment"`
+	Width        int               `json:"width"`
+	Height       int               `json:"height"`
+}
diff --git a/shared/api/container_snapshot.go b/shared/api/container_snapshot.go
new file mode 100644
index 0000000..ab65ab7
--- /dev/null
+++ b/shared/api/container_snapshot.go
@@ -0,0 +1,32 @@
+package api
+
+import (
+	"time"
+)
+
+// ContainerSnapshotsPost represents the fields available for a new LXD container snapshot
+type ContainerSnapshotsPost struct {
+	Name     string `json:"name"`
+	Stateful bool   `json:"stateful"`
+}
+
+// ContainerSnapshotPost represents the fields required to rename/move a LXD container snapshot
+type ContainerSnapshotPost struct {
+	Name      string `json:"name"`
+	Migration bool   `json:"migration"`
+}
+
+// ContainerSnapshot represents a LXD conainer snapshot
+type ContainerSnapshot struct {
+	Architecture    string                       `json:"architecture"`
+	Config          map[string]string            `json:"config"`
+	CreationDate    time.Time                    `json:"created_at"`
+	Devices         map[string]map[string]string `json:"devices"`
+	Ephemeral       bool                         `json:"ephemeral"`
+	ExpandedConfig  map[string]string            `json:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
+	LastUsedDate    time.Time                    `json:"last_used_at"`
+	Name            string                       `json:"name"`
+	Profiles        []string                     `json:"profiles"`
+	Stateful        bool                         `json:"stateful"`
+}
diff --git a/shared/api/container_state.go b/shared/api/container_state.go
new file mode 100644
index 0000000..43bc621
--- /dev/null
+++ b/shared/api/container_state.go
@@ -0,0 +1,66 @@
+package api
+
+// ContainerStatePut represents the modifiable fields of a LXD container's state
+type ContainerStatePut struct {
+	Action   string `json:"action"`
+	Timeout  int    `json:"timeout"`
+	Force    bool   `json:"force"`
+	Stateful bool   `json:"stateful"`
+}
+
+// ContainerState represents a LXD container's state
+type ContainerState struct {
+	Status     string                           `json:"status"`
+	StatusCode StatusCode                       `json:"status_code"`
+	CPU        ContainerStateCPU                `json:"cpu"`
+	Disk       map[string]ContainerStateDisk    `json:"disk"`
+	Memory     ContainerStateMemory             `json:"memory"`
+	Network    map[string]ContainerStateNetwork `json:"network"`
+	Pid        int64                            `json:"pid"`
+	Processes  int64                            `json:"processes"`
+}
+
+// ContainerStateDisk represents the disk information section of a LXD container's state
+type ContainerStateDisk struct {
+	Usage int64 `json:"usage"`
+}
+
+// ContainerStateCPU represents the cpu information section of a LXD container's state
+type ContainerStateCPU struct {
+	Usage int64 `json:"usage"`
+}
+
+// ContainerStateMemory represents the memory information section of a LXD container's state
+type ContainerStateMemory struct {
+	Usage         int64 `json:"usage"`
+	UsagePeak     int64 `json:"usage_peak"`
+	SwapUsage     int64 `json:"swap_usage"`
+	SwapUsagePeak int64 `json:"swap_usage_peak"`
+}
+
+// ContainerStateNetwork represents the network information section of a LXD container's state
+type ContainerStateNetwork struct {
+	Addresses []ContainerStateNetworkAddress `json:"addresses"`
+	Counters  ContainerStateNetworkCounters  `json:"counters"`
+	Hwaddr    string                         `json:"hwaddr"`
+	HostName  string                         `json:"host_name"`
+	Mtu       int                            `json:"mtu"`
+	State     string                         `json:"state"`
+	Type      string                         `json:"type"`
+}
+
+// ContainerStateNetworkAddress represents a network address as part of the network section of a LXD container's state
+type ContainerStateNetworkAddress struct {
+	Family  string `json:"family"`
+	Address string `json:"address"`
+	Netmask string `json:"netmask"`
+	Scope   string `json:"scope"`
+}
+
+// ContainerStateNetworkCounters represents packet counters as part of the network section of a LXD container's state
+type ContainerStateNetworkCounters struct {
+	BytesReceived   int64 `json:"bytes_received"`
+	BytesSent       int64 `json:"bytes_sent"`
+	PacketsReceived int64 `json:"packets_received"`
+	PacketsSent     int64 `json:"packets_sent"`
+}
diff --git a/shared/container.go b/shared/container.go
index ded95c4..34363c4 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -4,139 +4,8 @@ import (
 	"fmt"
 	"strconv"
 	"strings"
-	"syscall"
-	"time"
-
-	"github.com/lxc/lxd/shared/api"
 )
 
-type ContainerState struct {
-	Status     string                           `json:"status"`
-	StatusCode api.StatusCode                   `json:"status_code"`
-	CPU        ContainerStateCPU                `json:"cpu"`
-	Disk       map[string]ContainerStateDisk    `json:"disk"`
-	Memory     ContainerStateMemory             `json:"memory"`
-	Network    map[string]ContainerStateNetwork `json:"network"`
-	Pid        int64                            `json:"pid"`
-	Processes  int64                            `json:"processes"`
-}
-
-type ContainerStateDisk struct {
-	Usage int64 `json:"usage"`
-}
-
-type ContainerStateCPU struct {
-	Usage int64 `json:"usage"`
-}
-
-type ContainerStateMemory struct {
-	Usage         int64 `json:"usage"`
-	UsagePeak     int64 `json:"usage_peak"`
-	SwapUsage     int64 `json:"swap_usage"`
-	SwapUsagePeak int64 `json:"swap_usage_peak"`
-}
-
-type ContainerStateNetwork struct {
-	Addresses []ContainerStateNetworkAddress `json:"addresses"`
-	Counters  ContainerStateNetworkCounters  `json:"counters"`
-	Hwaddr    string                         `json:"hwaddr"`
-	HostName  string                         `json:"host_name"`
-	Mtu       int                            `json:"mtu"`
-	State     string                         `json:"state"`
-	Type      string                         `json:"type"`
-}
-
-type ContainerStateNetworkAddress struct {
-	Family  string `json:"family"`
-	Address string `json:"address"`
-	Netmask string `json:"netmask"`
-	Scope   string `json:"scope"`
-}
-
-type ContainerStateNetworkCounters struct {
-	BytesReceived   int64 `json:"bytes_received"`
-	BytesSent       int64 `json:"bytes_sent"`
-	PacketsReceived int64 `json:"packets_received"`
-	PacketsSent     int64 `json:"packets_sent"`
-}
-
-type ContainerExecControl struct {
-	Command string            `json:"command"`
-	Args    map[string]string `json:"args"`
-	Signal  syscall.Signal    `json:"signal"`
-}
-
-type SnapshotInfo struct {
-	Architecture    string                       `json:"architecture"`
-	Config          map[string]string            `json:"config"`
-	CreationDate    time.Time                    `json:"created_at"`
-	Devices         map[string]map[string]string `json:"devices"`
-	Ephemeral       bool                         `json:"ephemeral"`
-	ExpandedConfig  map[string]string            `json:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
-	LastUsedDate    time.Time                    `json:"last_used_at"`
-	Name            string                       `json:"name"`
-	Profiles        []string                     `json:"profiles"`
-	Stateful        bool                         `json:"stateful"`
-}
-
-type ContainerInfo struct {
-	Architecture    string                       `json:"architecture"`
-	Config          map[string]string            `json:"config"`
-	CreationDate    time.Time                    `json:"created_at"`
-	Devices         map[string]map[string]string `json:"devices"`
-	Ephemeral       bool                         `json:"ephemeral"`
-	ExpandedConfig  map[string]string            `json:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
-	LastUsedDate    time.Time                    `json:"last_used_at"`
-	Name            string                       `json:"name"`
-	Profiles        []string                     `json:"profiles"`
-	Stateful        bool                         `json:"stateful"`
-	Status          string                       `json:"status"`
-	StatusCode      api.StatusCode               `json:"status_code"`
-}
-
-func (c ContainerInfo) IsActive() bool {
-	switch c.StatusCode {
-	case api.Stopped:
-		return false
-	case api.Error:
-		return false
-	default:
-		return true
-	}
-}
-
-/*
- * BriefContainerState contains a subset of the fields in
- * ContainerState, namely those which a user may update
- */
-type BriefContainerInfo struct {
-	Name      string                       `json:"name"`
-	Profiles  []string                     `json:"profiles"`
-	Config    map[string]string            `json:"config"`
-	Devices   map[string]map[string]string `json:"devices"`
-	Ephemeral bool                         `json:"ephemeral"`
-}
-
-func (c *ContainerInfo) Brief() BriefContainerInfo {
-	retstate := BriefContainerInfo{Name: c.Name,
-		Profiles:  c.Profiles,
-		Config:    c.Config,
-		Devices:   c.Devices,
-		Ephemeral: c.Ephemeral}
-	return retstate
-}
-
-func (c *ContainerInfo) BriefExpanded() BriefContainerInfo {
-	retstate := BriefContainerInfo{Name: c.Name,
-		Profiles:  c.Profiles,
-		Config:    c.ExpandedConfig,
-		Devices:   c.ExpandedDevices,
-		Ephemeral: c.Ephemeral}
-	return retstate
-}
-
 type ContainerAction string
 
 const (
diff --git a/test/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
index dc06f60..e8f619c 100644
--- a/test/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 )
 
@@ -258,7 +259,7 @@ func deleteContainers(c *lxd.Client) error {
 		return err
 	}
 
-	containers := []shared.ContainerInfo{}
+	containers := []api.Container{}
 	for _, container := range allContainers {
 		if container.Config["user.lxd-benchmark"] != "true" {
 			continue
@@ -277,7 +278,7 @@ func deleteContainers(c *lxd.Client) error {
 	wgBatch := sync.WaitGroup{}
 	nextStat := batch
 
-	deleteContainer := func(ct shared.ContainerInfo) {
+	deleteContainer := func(ct api.Container) {
 		defer wgBatch.Done()
 
 		// Stop

From 063430d844c92d409fb78ec98aa30d74dd017d9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Dec 2016 17:12:21 -0500
Subject: [PATCH 9/9] shared: Move REST API to new package: response
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go              | 250 ++++++++++++++++++++-----------------------------
 lxc/action.go          |   2 +-
 lxc/config.go          |   4 +-
 lxc/copy.go            |   3 +-
 lxc/init.go            |   2 +-
 lxc/launch.go          |   3 +-
 lxd/daemon.go          |   7 +-
 lxd/daemon_images.go   |   3 +-
 lxd/main_callhook.go   |   3 +-
 lxd/main_import.go     |   3 +-
 lxd/main_ready.go      |   3 +-
 lxd/main_waitready.go  |   3 +-
 lxd/remote.go          |   3 +-
 lxd/response.go        |  42 ++++-----
 shared/api/response.go |  82 ++++++++++++++++
 15 files changed, 221 insertions(+), 192 deletions(-)
 create mode 100644 shared/api/response.go

diff --git a/client.go b/client.go
index 1635e50..4faf2e6 100644
--- a/client.go
+++ b/client.go
@@ -45,14 +45,6 @@ type Client struct {
 	simplestreams   *simplestreams.SimpleStreams
 }
 
-type ResponseType string
-
-const (
-	Sync  ResponseType = "sync"
-	Async ResponseType = "async"
-	Error ResponseType = "error"
-)
-
 var (
 	// LXDErrors are special errors; the client library hoists error codes
 	// to these errors internally so that user code can compare against
@@ -64,61 +56,17 @@ var (
 	}
 )
 
-type Response struct {
-	Type ResponseType `json:"type"`
-
-	/* Valid only for Sync responses */
-	Status     string `json:"status"`
-	StatusCode int    `json:"status_code"`
-
-	/* Valid only for Async responses */
-	Operation string `json:"operation"`
-
-	/* Valid only for Error responses */
-	Code  int    `json:"error_code"`
-	Error string `json:"error"`
-
-	/* Valid for Sync and Error responses */
-	Metadata json.RawMessage `json:"metadata"`
-}
-
-func (r *Response) MetadataAsMap() (*shared.Jmap, error) {
-	ret := shared.Jmap{}
-	if err := json.Unmarshal(r.Metadata, &ret); err != nil {
-		return nil, err
-	}
-	return &ret, nil
-}
-
-func (r *Response) MetadataAsOperation() (*api.Operation, error) {
-	op := api.Operation{}
-	if err := json.Unmarshal(r.Metadata, &op); err != nil {
-		return nil, err
-	}
-
-	return &op, nil
-}
-
-func (r *Response) MetadataAsStringSlice() ([]string, error) {
-	sl := []string{}
-	if err := json.Unmarshal(r.Metadata, &sl); err != nil {
-		return nil, err
-	}
-
-	return sl, nil
-}
-
 // ParseResponse parses a lxd style response out of an http.Response. Note that
 // this does _not_ automatically convert error responses to golang errors. To
 // do that, use ParseError. Internal client library uses should probably use
 // HoistResponse, unless they are interested in accessing the underlying Error
 // response (e.g. to inspect the error code).
-func ParseResponse(r *http.Response) (*Response, error) {
+func ParseResponse(r *http.Response) (*api.Response, error) {
 	if r == nil {
 		return nil, fmt.Errorf("no response!")
 	}
 	defer r.Body.Close()
-	ret := Response{}
+	ret := api.Response{}
 
 	s, err := ioutil.ReadAll(r.Body)
 	if err != nil {
@@ -135,13 +83,13 @@ func ParseResponse(r *http.Response) (*Response, error) {
 
 // HoistResponse hoists a regular http response into a response of type rtype
 // or returns a golang error.
-func HoistResponse(r *http.Response, rtype ResponseType) (*Response, error) {
+func HoistResponse(r *http.Response, rtype api.ResponseType) (*api.Response, error) {
 	resp, err := ParseResponse(r)
 	if err != nil {
 		return nil, err
 	}
 
-	if resp.Type == Error {
+	if resp.Type == api.ErrorResponse {
 		// Try and use a known error if we have one for this code.
 		err, ok := LXDErrors[resp.Code]
 		if !ok {
@@ -393,13 +341,13 @@ func (c *Client) Addresses() ([]string, error) {
 	return addresses, nil
 }
 
-func (c *Client) get(base string) (*Response, error) {
+func (c *Client) get(base string) (*api.Response, error) {
 	uri := c.url(version.APIVersion, base)
 
 	return c.baseGet(uri)
 }
 
-func (c *Client) baseGet(getUrl string) (*Response, error) {
+func (c *Client) baseGet(getUrl string) (*api.Response, error) {
 	req, err := http.NewRequest("GET", getUrl, nil)
 	if err != nil {
 		return nil, err
@@ -412,10 +360,10 @@ func (c *Client) baseGet(getUrl string) (*Response, error) {
 		return nil, err
 	}
 
-	return HoistResponse(resp, Sync)
+	return HoistResponse(resp, api.SyncResponse)
 }
 
-func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	uri := c.url(version.APIVersion, base)
 
 	buf := bytes.Buffer{}
@@ -441,19 +389,19 @@ func (c *Client) doUpdateMethod(method string, base string, args interface{}, rt
 	return HoistResponse(resp, rtype)
 }
 
-func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) put(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("PUT", base, args, rtype)
 }
 
-func (c *Client) patch(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) patch(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("PATCH", base, args, rtype)
 }
 
-func (c *Client) post(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) post(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("POST", base, args, rtype)
 }
 
-func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) delete(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("DELETE", base, args, rtype)
 }
 
@@ -471,7 +419,7 @@ func (c *Client) getRaw(uri string) (*http.Response, error) {
 
 	// because it is raw data, we need to check for http status
 	if raw.StatusCode != 200 {
-		resp, err := HoistResponse(raw, Sync)
+		resp, err := HoistResponse(raw, api.SyncResponse)
 		if err != nil {
 			return nil, err
 		}
@@ -518,7 +466,7 @@ func (c *Client) url(elem ...string) string {
 	return strings.TrimSuffix(uri, "/")
 }
 
-func (c *Client) GetServerConfig() (*Response, error) {
+func (c *Client) GetServerConfig() (*api.Response, error) {
 	if c.Remote.Protocol == "simplestreams" {
 		return nil, fmt.Errorf("This function isn't supported by simplestreams remote.")
 	}
@@ -575,12 +523,12 @@ func (c *Client) AmTrusted() bool {
 
 	shared.LogDebugf("%s", resp)
 
-	jmap, err := resp.MetadataAsMap()
+	meta, err := resp.MetadataAsMap()
 	if err != nil {
 		return false
 	}
 
-	auth, err := jmap.GetString("auth")
+	auth, err := shared.Jmap(meta).GetString("auth")
 	if err != nil {
 		return false
 	}
@@ -596,12 +544,12 @@ func (c *Client) IsPublic() bool {
 
 	shared.LogDebugf("%s", resp)
 
-	jmap, err := resp.MetadataAsMap()
+	meta, err := resp.MetadataAsMap()
 	if err != nil {
 		return false
 	}
 
-	public, err := jmap.GetBool("public")
+	public, err := shared.Jmap(meta).GetBool("public")
 	if err != nil {
 		return false
 	}
@@ -621,7 +569,7 @@ func (c *Client) ListContainers() ([]api.Container, error) {
 
 	var result []api.Container
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -650,7 +598,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 	if c.Remote.Protocol != "simplestreams" && !info.Public {
 		var secret string
 
-		resp, err := c.post("images/"+image+"/secret", nil, Async)
+		resp, err := c.post("images/"+image+"/secret", nil, api.AsyncResponse)
 		if err != nil {
 			return err
 		}
@@ -717,7 +665,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		source["server"] = sourceUrl
 		body := shared.Jmap{"public": public, "auto_update": autoUpdate, "source": source}
 
-		resp, err := dest.post("images", body, Async)
+		resp, err := dest.post("images", body, api.AsyncResponse)
 		if err != nil {
 			continue
 		}
@@ -941,7 +889,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 		go c.Monitor([]string{"operation"}, handler, nil)
 	}
 
-	resp, err := c.post("images", body, Async)
+	resp, err := c.post("images", body, api.AsyncResponse)
 	if err != nil {
 		return "", err
 	}
@@ -1110,7 +1058,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		return "", err
 	}
 
-	resp, err := HoistResponse(raw, Async)
+	resp, err := HoistResponse(raw, api.AsyncResponse)
 	if err != nil {
 		return "", err
 	}
@@ -1148,7 +1096,7 @@ func (c *Client) GetImageInfo(image string) (*api.Image, error) {
 	}
 
 	info := api.Image{}
-	if err := json.Unmarshal(resp.Metadata, &info); err != nil {
+	if err := resp.MetadataAsStruct(&info); err != nil {
 		return nil, err
 	}
 
@@ -1160,7 +1108,7 @@ func (c *Client) PutImageInfo(name string, p api.ImagePut) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.put(fmt.Sprintf("images/%s", name), p, Sync)
+	_, err := c.put(fmt.Sprintf("images/%s", name), p, api.SyncResponse)
 	return err
 }
 
@@ -1175,7 +1123,7 @@ func (c *Client) ListImages() ([]api.Image, error) {
 	}
 
 	var result []api.Image
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1187,7 +1135,7 @@ func (c *Client) DeleteImage(image string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	resp, err := c.delete(fmt.Sprintf("images/%s", image), nil, Async)
+	resp, err := c.delete(fmt.Sprintf("images/%s", image), nil, api.AsyncResponse)
 
 	if err != nil {
 		return err
@@ -1203,7 +1151,7 @@ func (c *Client) PostAlias(alias string, desc string, target string) error {
 
 	body := shared.Jmap{"description": desc, "target": target, "name": alias}
 
-	_, err := c.post("images/aliases", body, Sync)
+	_, err := c.post("images/aliases", body, api.SyncResponse)
 	return err
 }
 
@@ -1212,7 +1160,7 @@ func (c *Client) DeleteAlias(alias string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("images/aliases/%s", alias), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("images/aliases/%s", alias), nil, api.SyncResponse)
 	return err
 }
 
@@ -1228,7 +1176,7 @@ func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) {
 
 	var result []api.ImageAliasesEntry
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1246,7 +1194,7 @@ func (c *Client) CertificateList() ([]api.Certificate, error) {
 	}
 
 	var result []api.Certificate
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1260,7 +1208,7 @@ func (c *Client) AddMyCertToServer(pwd string) error {
 
 	body := shared.Jmap{"type": "client", "password": pwd}
 
-	_, err := c.post("certificates", body, Sync)
+	_, err := c.post("certificates", body, api.SyncResponse)
 	return err
 }
 
@@ -1270,7 +1218,7 @@ func (c *Client) CertificateAdd(cert *x509.Certificate, name string) error {
 	}
 
 	b64 := base64.StdEncoding.EncodeToString(cert.Raw)
-	_, err := c.post("certificates", shared.Jmap{"type": "client", "certificate": b64, "name": name}, Sync)
+	_, err := c.post("certificates", shared.Jmap{"type": "client", "certificate": b64, "name": name}, api.SyncResponse)
 	return err
 }
 
@@ -1279,7 +1227,7 @@ func (c *Client) CertificateRemove(fingerprint string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("certificates/%s", fingerprint), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("certificates/%s", fingerprint), nil, api.SyncResponse)
 	return err
 }
 
@@ -1305,12 +1253,12 @@ func (c *Client) GetAlias(alias string) string {
 		return ""
 	}
 
-	if resp.Type == Error {
+	if resp.Type == api.ErrorResponse {
 		return ""
 	}
 
 	var result api.ImageAliasesEntry
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return ""
 	}
 	return result.Target
@@ -1318,7 +1266,7 @@ func (c *Client) GetAlias(alias string) string {
 
 // Init creates a container from either a fingerprint or an alias; you must
 // provide at least one.
-func (c *Client) Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, devices map[string]map[string]string, ephem bool) (*Response, error) {
+func (c *Client) Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, devices map[string]map[string]string, ephem bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1366,7 +1314,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 
 				image = target
 
-				resp, err := tmpremote.post("images/"+image+"/secret", nil, Async)
+				resp, err := tmpremote.post("images/"+image+"/secret", nil, api.AsyncResponse)
 				if err != nil {
 					return nil, err
 				}
@@ -1428,7 +1376,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 		body["ephemeral"] = ephem
 	}
 
-	var resp *Response
+	var resp *api.Response
 
 	if imgremote != c.Name {
 		var addresses []string
@@ -1440,7 +1388,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 		for _, addr := range addresses {
 			body["source"].(shared.Jmap)["server"] = "https://" + addr
 
-			resp, err = c.post("containers", body, Async)
+			resp, err = c.post("containers", body, api.AsyncResponse)
 			if err != nil {
 				continue
 			}
@@ -1448,7 +1396,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 			break
 		}
 	} else {
-		resp, err = c.post("containers", body, Async)
+		resp, err = c.post("containers", body, api.AsyncResponse)
 	}
 
 	if err != nil {
@@ -1461,7 +1409,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 	return resp, nil
 }
 
-func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*Response, error) {
+func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1477,7 +1425,7 @@ func (c *Client) LocalCopy(source string, name string, config map[string]string,
 		"ephemeral": ephemeral,
 	}
 
-	return c.post("containers", body, Async)
+	return c.post("containers", body, api.AsyncResponse)
 }
 
 func (c *Client) Monitor(types []string, handler func(interface{}), done chan bool) error {
@@ -1556,7 +1504,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		body["height"] = height
 	}
 
-	resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, Async)
+	resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, api.AsyncResponse)
 	if err != nil {
 		return -1, err
 	}
@@ -1658,7 +1606,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 	return shared.Jmap(op.Metadata).GetInt("return")
 }
 
-func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*Response, error) {
+func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1672,10 +1620,10 @@ func (c *Client) Action(name string, action shared.ContainerAction, timeout int,
 		body["stateful"] = stateful
 	}
 
-	return c.put(fmt.Sprintf("containers/%s/state", name), body, Async)
+	return c.put(fmt.Sprintf("containers/%s/state", name), body, api.AsyncResponse)
 }
 
-func (c *Client) Delete(name string) (*Response, error) {
+func (c *Client) Delete(name string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1688,7 +1636,7 @@ func (c *Client) Delete(name string) (*Response, error) {
 		url = fmt.Sprintf("containers/%s", name)
 	}
 
-	return c.delete(url, nil, Async)
+	return c.delete(url, nil, api.AsyncResponse)
 }
 
 func (c *Client) ServerStatus() (*api.Server, error) {
@@ -1699,7 +1647,7 @@ func (c *Client) ServerStatus() (*api.Server, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ss); err != nil {
+	if err := resp.MetadataAsStruct(&ss); err != nil {
 		return nil, err
 	}
 
@@ -1727,7 +1675,7 @@ func (c *Client) ContainerInfo(name string) (*api.Container, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ct); err != nil {
+	if err := resp.MetadataAsStruct(&ct); err != nil {
 		return nil, err
 	}
 
@@ -1746,7 +1694,7 @@ func (c *Client) ContainerState(name string) (*api.ContainerState, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ct); err != nil {
+	if err := resp.MetadataAsStruct(&ct); err != nil {
 		return nil, err
 	}
 
@@ -1779,7 +1727,7 @@ func (c *Client) ProfileConfig(name string) (*api.Profile, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ct); err != nil {
+	if err := resp.MetadataAsStruct(&ct); err != nil {
 		return nil, err
 	}
 
@@ -1816,7 +1764,7 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode str
 		return err
 	}
 
-	_, err = HoistResponse(raw, Sync)
+	_, err = HoistResponse(raw, api.SyncResponse)
 	return err
 }
 
@@ -1842,7 +1790,7 @@ func (c *Client) Mkdir(container string, p string, mode os.FileMode) error {
 		return err
 	}
 
-	_, err = HoistResponse(raw, Sync)
+	_, err = HoistResponse(raw, api.SyncResponse)
 	return err
 }
 
@@ -1935,7 +1883,7 @@ func (c *Client) PullFile(container string, p string) (int, int, int, string, io
 
 	uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)
 	if type_ == "directory" {
-		resp, err := HoistResponse(r, Sync)
+		resp, err := HoistResponse(r, api.SyncResponse)
 		if err != nil {
 			return 0, 0, 0, "", nil, nil, err
 		}
@@ -1999,7 +1947,7 @@ func (c *Client) RecursivePullFile(container string, p string, targetDir string)
 	return nil
 }
 
-func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
+func (c *Client) GetMigrationSourceWS(container string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2015,14 +1963,14 @@ func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
 		url = fmt.Sprintf("containers/%s/snapshots/%s", pieces[0], pieces[1])
 	}
 
-	return c.post(url, body, Async)
+	return c.post(url, body, api.AsyncResponse)
 }
 
 func (c *Client) MigrateFrom(name string, operation string, certificate string,
 	sourceSecrets map[string]string, architecture string, config map[string]string,
 	devices map[string]map[string]string, profiles []string,
 	baseImage string, ephemeral bool, push bool, sourceClient *Client,
-	sourceOperation string) (*Response, error) {
+	sourceOperation string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2094,7 +2042,7 @@ func (c *Client) MigrateFrom(name string, operation string, certificate string,
 
 		// Post to target server and request and retrieve a set of
 		// websockets + secrets matching those of the source server.
-		resp, err := c.post("containers", body, Async)
+		resp, err := c.post("containers", body, api.AsyncResponse)
 		if err != nil {
 			return nil, err
 		}
@@ -2192,10 +2140,10 @@ func (c *Client) MigrateFrom(name string, operation string, certificate string,
 		return resp, nil
 	}
 
-	return c.post("containers", body, Async)
+	return c.post("containers", body, api.AsyncResponse)
 }
 
-func (c *Client) Rename(name string, newName string) (*Response, error) {
+func (c *Client) Rename(name string, newName string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2207,13 +2155,13 @@ func (c *Client) Rename(name string, newName string) (*Response, error) {
 	}
 	if len(oldNameParts) == 1 {
 		body := shared.Jmap{"name": newName}
-		return c.post(fmt.Sprintf("containers/%s", name), body, Async)
+		return c.post(fmt.Sprintf("containers/%s", name), body, api.AsyncResponse)
 	}
 	if oldNameParts[0] != newNameParts[0] {
 		return nil, fmt.Errorf("Attempting to rename snapshot of one container into a snapshot of another container.")
 	}
 	body := shared.Jmap{"name": newNameParts[1]}
-	return c.post(fmt.Sprintf("containers/%s/snapshots/%s", oldNameParts[0], oldNameParts[1]), body, Async)
+	return c.post(fmt.Sprintf("containers/%s/snapshots/%s", oldNameParts[0], oldNameParts[1]), body, api.AsyncResponse)
 }
 
 /* Wait for an operation */
@@ -2262,22 +2210,22 @@ func (c *Client) WaitForSuccessOp(waitURL string) (*api.Operation, error) {
 	return op, fmt.Errorf(op.Err)
 }
 
-func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*Response, error) {
+func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
 	body := shared.Jmap{"restore": snapshotName, "stateful": stateful}
-	return c.put(fmt.Sprintf("containers/%s", container), body, Async)
+	return c.put(fmt.Sprintf("containers/%s", container), body, api.AsyncResponse)
 }
 
-func (c *Client) Snapshot(container string, snapshotName string, stateful bool) (*Response, error) {
+func (c *Client) Snapshot(container string, snapshotName string, stateful bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
 	body := shared.Jmap{"name": snapshotName, "stateful": stateful}
-	return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, Async)
+	return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, api.AsyncResponse)
 }
 
 func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error) {
@@ -2293,7 +2241,7 @@ func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error
 
 	var result []api.ContainerSnapshot
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -2318,7 +2266,7 @@ func (c *Client) SnapshotInfo(snapName string) (*api.ContainerSnapshot, error) {
 
 	var result api.ContainerSnapshot
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -2348,7 +2296,7 @@ func (c *Client) GetServerConfigString() ([]string, error) {
 	return resp, nil
 }
 
-func (c *Client) SetServerConfig(key string, value string) (*Response, error) {
+func (c *Client) SetServerConfig(key string, value string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2360,15 +2308,15 @@ func (c *Client) SetServerConfig(key string, value string) (*Response, error) {
 
 	ss.Config[key] = value
 
-	return c.put("", ss, Sync)
+	return c.put("", ss, api.SyncResponse)
 }
 
-func (c *Client) UpdateServerConfig(ss api.ServerPut) (*Response, error) {
+func (c *Client) UpdateServerConfig(ss api.ServerPut) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	return c.put("", ss, Sync)
+	return c.put("", ss, api.SyncResponse)
 }
 
 /*
@@ -2419,7 +2367,7 @@ func (c *Client) SetContainerConfig(container, key, value string) error {
 	 * snapshot), we expect config to be a sync operation, so let's just
 	 * handle it here.
 	 */
-	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 	if err != nil {
 		return err
 	}
@@ -2432,7 +2380,7 @@ func (c *Client) UpdateContainerConfig(container string, st api.ContainerPut) er
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 	if err != nil {
 		return err
 	}
@@ -2447,7 +2395,7 @@ func (c *Client) ProfileCreate(p string) error {
 
 	body := shared.Jmap{"name": p}
 
-	_, err := c.post("profiles", body, Sync)
+	_, err := c.post("profiles", body, api.SyncResponse)
 	return err
 }
 
@@ -2456,7 +2404,7 @@ func (c *Client) ProfileDelete(p string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("profiles/%s", p), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("profiles/%s", p), nil, api.SyncResponse)
 	return err
 }
 
@@ -2490,7 +2438,7 @@ func (c *Client) SetProfileConfigItem(profile, key, value string) error {
 		st.Config[key] = value
 	}
 
-	_, err = c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+	_, err = c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
 	return err
 }
 
@@ -2499,7 +2447,7 @@ func (c *Client) PutProfile(name string, profile api.ProfilePut) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.put(fmt.Sprintf("profiles/%s", name), profile, Sync)
+	_, err := c.put(fmt.Sprintf("profiles/%s", name), profile, api.SyncResponse)
 	return err
 }
 
@@ -2514,14 +2462,14 @@ func (c *Client) ListProfiles() ([]api.Profile, error) {
 	}
 
 	profiles := []api.Profile{}
-	if err := json.Unmarshal(resp.Metadata, &profiles); err != nil {
+	if err := resp.MetadataAsStruct(&profiles); err != nil {
 		return nil, err
 	}
 
 	return profiles, nil
 }
 
-func (c *Client) AssignProfile(container, profile string) (*Response, error) {
+func (c *Client) AssignProfile(container, profile string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2537,10 +2485,10 @@ func (c *Client) AssignProfile(container, profile string) (*Response, error) {
 		st.Profiles = nil
 	}
 
-	return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 }
 
-func (c *Client) ContainerDeviceDelete(container, devname string) (*Response, error) {
+func (c *Client) ContainerDeviceDelete(container, devname string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2553,14 +2501,14 @@ func (c *Client) ContainerDeviceDelete(container, devname string) (*Response, er
 	for n, _ := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
-			return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+			return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 		}
 	}
 
 	return nil, fmt.Errorf("Device doesn't exist.")
 }
 
-func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*Response, error) {
+func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2592,7 +2540,7 @@ func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []
 
 	st.Devices[devname] = newdev
 
-	return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 }
 
 func (c *Client) ContainerListDevices(container string) ([]string, error) {
@@ -2611,7 +2559,7 @@ func (c *Client) ContainerListDevices(container string) ([]string, error) {
 	return devs, nil
 }
 
-func (c *Client) ProfileDeviceDelete(profile, devname string) (*Response, error) {
+func (c *Client) ProfileDeviceDelete(profile, devname string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2624,14 +2572,14 @@ func (c *Client) ProfileDeviceDelete(profile, devname string) (*Response, error)
 	for n, _ := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
-			return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+			return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
 		}
 	}
 
 	return nil, fmt.Errorf("Device doesn't exist.")
 }
 
-func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*Response, error) {
+func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2663,7 +2611,7 @@ func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []stri
 
 	st.Devices[devname] = newdev
 
-	return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+	return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
 }
 
 func (c *Client) ProfileListDevices(profile string) ([]string, error) {
@@ -2687,7 +2635,7 @@ func (c *Client) ProfileListDevices(profile string) ([]string, error) {
 func WebsocketDial(dialer websocket.Dialer, url string) (*websocket.Conn, error) {
 	conn, raw, err := dialer.Dial(url, http.Header{})
 	if err != nil {
-		_, err2 := HoistResponse(raw, Error)
+		_, err2 := HoistResponse(raw, api.ErrorResponse)
 		if err2 != nil {
 			/* The response isn't one we understand, so return
 			 * whatever the original error was. */
@@ -2710,11 +2658,11 @@ func (c *Client) ProfileCopy(name, newname string, dest *Client) error {
 	}
 
 	body := shared.Jmap{"config": st.Config, "name": newname, "devices": st.Devices}
-	_, err = dest.post("profiles", body, Sync)
+	_, err = dest.post("profiles", body, api.SyncResponse)
 	return err
 }
 
-func (c *Client) AsyncWaitMeta(resp *Response) (map[string]interface{}, error) {
+func (c *Client) AsyncWaitMeta(resp *api.Response) (map[string]interface{}, error) {
 	op, err := c.WaitFor(resp.Operation)
 	if err != nil {
 		return nil, err
@@ -2746,7 +2694,7 @@ func (c *Client) ImageFromContainer(cname string, public bool, aliases []string,
 		body["compression_algorithm"] = compression_algorithm
 	}
 
-	resp, err := c.post("images", body, Async)
+	resp, err := c.post("images", body, api.AsyncResponse)
 	if err != nil {
 		return "", err
 	}
@@ -2781,7 +2729,7 @@ func (c *Client) NetworkCreate(name string, config map[string]string) error {
 
 	body := shared.Jmap{"name": name, "config": config}
 
-	_, err := c.post("networks", body, Sync)
+	_, err := c.post("networks", body, api.SyncResponse)
 	return err
 }
 
@@ -2796,7 +2744,7 @@ func (c *Client) NetworkGet(name string) (api.Network, error) {
 	}
 
 	network := api.Network{}
-	if err := json.Unmarshal(resp.Metadata, &network); err != nil {
+	if err := resp.MetadataAsStruct(&network); err != nil {
 		return api.Network{}, err
 	}
 
@@ -2808,7 +2756,7 @@ func (c *Client) NetworkPut(name string, network api.NetworkPut) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.put(fmt.Sprintf("networks/%s", name), network, Sync)
+	_, err := c.put(fmt.Sprintf("networks/%s", name), network, api.SyncResponse)
 	return err
 }
 
@@ -2817,7 +2765,7 @@ func (c *Client) NetworkDelete(name string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("networks/%s", name), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("networks/%s", name), nil, api.SyncResponse)
 	return err
 }
 
@@ -2832,7 +2780,7 @@ func (c *Client) ListNetworks() ([]api.Network, error) {
 	}
 
 	networks := []api.Network{}
-	if err := json.Unmarshal(resp.Metadata, &networks); err != nil {
+	if err := resp.MetadataAsStruct(&networks); err != nil {
 		return nil, err
 	}
 
diff --git a/lxc/action.go b/lxc/action.go
index ab66cfe..238cad9 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -92,7 +92,7 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		if resp.Type != lxd.Async {
+		if resp.Type != api.AsyncResponse {
 			return fmt.Errorf(i18n.G("bad result type from action"))
 		}
 
diff --git a/lxc/config.go b/lxc/config.go
index 0f2ad48..3314ccf 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -633,7 +633,7 @@ func (c *configCmd) deviceAdd(config *lxd.Config, which string, args []string) e
 		props = []string{}
 	}
 
-	var resp *lxd.Response
+	var resp *api.Response
 	if which == "profile" {
 		resp, err = client.ProfileDeviceAdd(name, devname, devtype, props)
 	} else {
@@ -820,7 +820,7 @@ func (c *configCmd) deviceRm(config *lxd.Config, which string, args []string) er
 	}
 
 	devname := args[3]
-	var resp *lxd.Response
+	var resp *api.Response
 	if which == "profile" {
 		resp, err = client.ProfileDeviceDelete(name, devname)
 	} else {
diff --git a/lxc/copy.go b/lxc/copy.go
index 4c98735..efdb155 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -207,7 +208,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	 * report that.
 	 */
 	for _, addr := range addresses {
-		var migration *lxd.Response
+		var migration *api.Response
 
 		sourceWSUrl := "https://" + addr + sourceWSResponse.Operation
 		migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1, false, source, sourceWSResponse.Operation)
diff --git a/lxc/init.go b/lxc/init.go
index c94c225..0e6df1d 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -173,7 +173,7 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 		profiles = append(profiles, p)
 	}
 
-	var resp *lxd.Response
+	var resp *api.Response
 	if name == "" {
 		fmt.Printf(i18n.G("Creating the container") + "\n")
 	} else {
diff --git a/lxc/launch.go b/lxc/launch.go
index 1f8a414..fd3d531 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -62,7 +63,7 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 	 * initRequestedEmptyProfiles means user requested empty
 	 * !initRequestedEmptyProfiles but len(profArgs) == 0 means use profile default
 	 */
-	var resp *lxd.Response
+	var resp *api.Response
 	profiles := []string{}
 	for _, p := range c.init.profArgs {
 		profiles = append(profiles, p)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 80c39da..7d565a5 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -31,6 +31,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -144,7 +145,7 @@ func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 	return &myhttp, nil
 }
 
-func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) {
+func (d *Daemon) httpGetSync(url string, certificate string) (*api.Response, error) {
 	var err error
 
 	req, err := http.NewRequest("GET", url, nil)
@@ -169,7 +170,7 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 		return nil, err
 	}
 
-	if resp.Type != lxd.Sync {
+	if resp.Type != api.SyncResponse {
 		return nil, fmt.Errorf("unexpected non-sync response")
 	}
 
@@ -197,7 +198,7 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 	}
 
 	if raw.StatusCode != 200 {
-		_, err := lxd.HoistResponse(raw, lxd.Error)
+		_, err := lxd.HoistResponse(raw, api.ErrorResponse)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 6a8c7ee..3998d7b 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -292,7 +291,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return "", err
 		}
 
-		if err := json.Unmarshal(resp.Metadata, &info); err != nil {
+		if err := resp.MetadataAsStruct(&info); err != nil {
 			return "", err
 		}
 
diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
index b8e2737..31e2231 100644
--- a/lxd/main_callhook.go
+++ b/lxd/main_callhook.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdCallHook(args []string) error {
@@ -52,7 +53,7 @@ func cmdCallHook(args []string) error {
 			return
 		}
 
-		_, err = lxd.HoistResponse(raw, lxd.Sync)
+		_, err = lxd.HoistResponse(raw, api.SyncResponse)
 		if err != nil {
 			hook <- err
 			return
diff --git a/lxd/main_import.go b/lxd/main_import.go
index d7c6b7e..2e74863 100644
--- a/lxd/main_import.go
+++ b/lxd/main_import.go
@@ -5,6 +5,7 @@ import (
 	"net/http"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdImport(args []string) error {
@@ -23,7 +24,7 @@ func cmdImport(args []string) error {
 	}
 
 	raw, err := c.Http.Do(req)
-	_, err = lxd.HoistResponse(raw, lxd.Sync)
+	_, err = lxd.HoistResponse(raw, api.SyncResponse)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main_ready.go b/lxd/main_ready.go
index 4e4ebfb..1093234 100644
--- a/lxd/main_ready.go
+++ b/lxd/main_ready.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdReady() error {
@@ -22,7 +23,7 @@ func cmdReady() error {
 		return err
 	}
 
-	_, err = lxd.HoistResponse(raw, lxd.Sync)
+	_, err = lxd.HoistResponse(raw, api.SyncResponse)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main_waitready.go b/lxd/main_waitready.go
index 7e2b4cf..74534b3 100644
--- a/lxd/main_waitready.go
+++ b/lxd/main_waitready.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdWaitReady() error {
@@ -38,7 +39,7 @@ func cmdWaitReady() error {
 				continue
 			}
 
-			_, err = lxd.HoistResponse(raw, lxd.Sync)
+			_, err = lxd.HoistResponse(raw, api.SyncResponse)
 			if err != nil {
 				time.Sleep(500 * time.Millisecond)
 				continue
diff --git a/lxd/remote.go b/lxd/remote.go
index 0a42483..da8bfc4 100644
--- a/lxd/remote.go
+++ b/lxd/remote.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 
 	"github.com/lxc/lxd/shared/api"
@@ -19,7 +18,7 @@ func remoteGetImageFingerprint(d *Daemon, server string, certificate string, ali
 	}
 
 	var result api.ImageAliasesEntry
-	if err = json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err = resp.MetadataAsStruct(&result); err != nil {
 		return "", fmt.Errorf("Error reading alias")
 	}
 
diff --git a/lxd/response.go b/lxd/response.go
index 062e8e1..bf1731c 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -13,26 +13,10 @@ import (
 
 	"github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 
-type syncResp struct {
-	Type       lxd.ResponseType `json:"type"`
-	Status     string           `json:"status"`
-	StatusCode api.StatusCode   `json:"status_code"`
-	Metadata   interface{}      `json:"metadata"`
-}
-
-type asyncResp struct {
-	Type       lxd.ResponseType `json:"type"`
-	Status     string           `json:"status"`
-	StatusCode api.StatusCode   `json:"status_code"`
-	Metadata   interface{}      `json:"metadata"`
-	Operation  string           `json:"operation"`
-}
-
 type Response interface {
 	Render(w http.ResponseWriter) error
 	String() string
@@ -73,7 +57,14 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
 		w.WriteHeader(201)
 	}
 
-	resp := syncResp{Type: lxd.Sync, Status: status.String(), StatusCode: status, Metadata: r.metadata}
+	resp := api.ResponseRaw{
+		Response: api.Response{
+			Type:       api.SyncResponse,
+			Status:     status.String(),
+			StatusCode: int(status)},
+		Metadata: r.metadata,
+	}
+
 	return WriteJSON(w, resp)
 }
 
@@ -232,12 +223,15 @@ func (r *operationResponse) Render(w http.ResponseWriter) error {
 		return err
 	}
 
-	body := asyncResp{
-		Type:       lxd.Async,
-		Status:     api.OperationCreated.String(),
-		StatusCode: api.OperationCreated,
-		Operation:  url,
-		Metadata:   md}
+	body := api.ResponseRaw{
+		Response: api.Response{
+			Type:       api.AsyncResponse,
+			Status:     api.OperationCreated.String(),
+			StatusCode: int(api.OperationCreated),
+			Operation:  url,
+		},
+		Metadata: md,
+	}
 
 	w.Header().Set("Location", url)
 	w.WriteHeader(202)
@@ -279,7 +273,7 @@ func (r *errorResponse) Render(w http.ResponseWriter) error {
 		output = io.MultiWriter(buf, captured)
 	}
 
-	err := json.NewEncoder(output).Encode(shared.Jmap{"type": lxd.Error, "error": r.msg, "error_code": r.code})
+	err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code})
 
 	if err != nil {
 		return err
diff --git a/shared/api/response.go b/shared/api/response.go
new file mode 100644
index 0000000..0515985
--- /dev/null
+++ b/shared/api/response.go
@@ -0,0 +1,82 @@
+package api
+
+import (
+	"encoding/json"
+)
+
+// ResponseRaw represents a LXD operation in its original form
+type ResponseRaw struct {
+	Response
+
+	Metadata interface{} `json:"metadata"`
+}
+
+// Response represents a LXD operation
+type Response struct {
+	Type ResponseType `json:"type"`
+
+	/* Valid only for Sync responses */
+	Status     string `json:"status"`
+	StatusCode int    `json:"status_code"`
+
+	/* Valid only for Async responses */
+	Operation string `json:"operation"`
+
+	/* Valid only for Error responses */
+	Code  int    `json:"error_code"`
+	Error string `json:"error"`
+
+	/* Valid for Sync and Error responses */
+	Metadata json.RawMessage `json:"metadata"`
+}
+
+// MetadataAsMap parses the Response metadata into a map
+func (r *Response) MetadataAsMap() (map[string]interface{}, error) {
+	ret := map[string]interface{}{}
+	err := r.MetadataAsStruct(&ret)
+	if err != nil {
+		return nil, err
+	}
+
+	return ret, nil
+}
+
+// MetadataAsMap turns the Response metadata into an Operation
+func (r *Response) MetadataAsOperation() (*Operation, error) {
+	op := Operation{}
+	err := r.MetadataAsStruct(&op)
+	if err != nil {
+		return nil, err
+	}
+
+	return &op, nil
+}
+
+// MetadataAsMap parses the Response metadata into a slice of string
+func (r *Response) MetadataAsStringSlice() ([]string, error) {
+	sl := []string{}
+	err := r.MetadataAsStruct(&sl)
+	if err != nil {
+		return nil, err
+	}
+
+	return sl, nil
+}
+
+// MetadataAsMap parses the Response metadata into a provided struct
+func (r *Response) MetadataAsStruct(target interface{}) error {
+	if err := json.Unmarshal(r.Metadata, &target); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ResponseType represents a valid LXD response type
+type ResponseType string
+
+const (
+	SyncResponse  ResponseType = "sync"
+	AsyncResponse ResponseType = "async"
+	ErrorResponse ResponseType = "error"
+)


More information about the lxc-devel mailing list