The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/1588

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From a3f49ffaecb82e280134e517494cb74251a1e7b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 10 Feb 2016 11:24:41 -0500
Subject: [PATCH 1/6] Remove backward compatibility code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This has been around for a long time now and nobody should be using
those old versions anymore, so lets clean things up before we get to
2.0.

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 client.go       | 63 ++++++++++++++++++---------------------------------------
 lxc/copy.go     | 52 ++++++++---------------------------------------
 lxc/image.go    |  6 ++----
 lxd/images.go   |  5 +----
 lxd/migrate.go  | 15 ++------------
 shared/image.go | 30 ++++++++++-----------------
 shared/util.go  | 16 ---------------
 7 files changed, 44 insertions(+), 143 deletions(-)

diff --git a/client.go b/client.go
index 6bb9550..136b96c 100644
--- a/client.go
+++ b/client.go
@@ -530,8 +530,7 @@ func (c *Client) CopyImage(image string, dest *Client, 
copy_aliases bool, aliase
                "server":      c.BaseURL,
                "fingerprint": fingerprint}
 
-       // FIXME: InterfaceToBool is there for backward compatibility
-       if !shared.InterfaceToBool(info.Public) {
+       if !info.Public {
                var secret string
 
                resp, err := c.post("images/"+fingerprint+"/secret", nil, Async)
@@ -540,19 +539,13 @@ func (c *Client) CopyImage(image string, dest *Client, 
copy_aliases bool, aliase
                }
 
                op, err := resp.MetadataAsOperation()
-               if err == nil && op.Metadata != nil {
-                       secret, err = op.Metadata.GetString("secret")
-                       if err != nil {
-                               return err
-                       }
-               } else {
-                       // FIXME: This is a backward compatibility codepath
-                       md := secretMd{}
-                       if err := json.Unmarshal(resp.Metadata, &md); err != 
nil {
-                               return err
-                       }
+               if err != nil {
+                       return err
+               }
 
-                       secret = md.Secret
+               secret, err = op.Metadata.GetString("secret")
+               if err != nil {
+                       return err
                }
 
                source["secret"] = secret
@@ -1158,8 +1151,7 @@ func (c *Client) Init(name string, imgremote string, 
image string, profiles *[]s
                        return nil, fmt.Errorf("The image architecture is 
incompatible with the target server")
                }
 
-               // FIXME: InterfaceToBool is there for backward compatibility
-               if !shared.InterfaceToBool(imageinfo.Public) {
+               if !imageinfo.Public {
                        var secret string
 
                        resp, err := 
tmpremote.post("images/"+fingerprint+"/secret", nil, Async)
@@ -1168,19 +1160,13 @@ func (c *Client) Init(name string, imgremote string, 
image string, profiles *[]s
                        }
 
                        op, err := resp.MetadataAsOperation()
-                       if err == nil && op.Metadata != nil {
-                               secret, err = op.Metadata.GetString("secret")
-                               if err != nil {
-                                       return nil, err
-                               }
-                       } else {
-                               // FIXME: This is a backward compatibility 
codepath
-                               md := secretMd{}
-                               if err := json.Unmarshal(resp.Metadata, &md); 
err != nil {
-                                       return nil, err
-                               }
+                       if err != nil {
+                               return nil, err
+                       }
 
-                               secret = md.Secret
+                       secret, err = op.Metadata.GetString("secret")
+                       if err != nil {
+                               return nil, err
                        }
 
                        source["secret"] = secret
@@ -1332,22 +1318,13 @@ func (c *Client) Exec(name string, cmd []string, env 
map[string]string,
        var fds shared.Jmap
 
        op, err := resp.MetadataAsOperation()
-       if err == nil && op.Metadata != nil {
-               fds, err = op.Metadata.GetMap("fds")
-               if err != nil {
-                       return -1, err
-               }
-       } else {
-               // FIXME: This is a backward compatibility codepath
-               md := execMd{}
-               if err := json.Unmarshal(resp.Metadata, &md); err != nil {
-                       return -1, err
-               }
+       if err != nil {
+               return -1, err
+       }
 
-               fds, err = shared.ParseMetadata(md.FDs)
-               if err != nil {
-                       return -1, err
-               }
+       fds, err = op.Metadata.GetMap("fds")
+       if err != nil {
+               return -1, err
        }
 
        if controlHandler != nil {
diff --git a/lxc/copy.go b/lxc/copy.go
index a07480e..0f3d20d 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-       "encoding/json"
        "fmt"
        "strings"
 
@@ -122,15 +121,12 @@ func copyContainer(config *lxd.Config, sourceResource 
string, destResource strin
                secrets := map[string]string{}
 
                op, err := sourceWSResponse.MetadataAsOperation()
-               if err == nil && op.Metadata != nil {
-                       for k, v := range *op.Metadata {
-                               secrets[k] = v.(string)
-                       }
-               } else {
-                       // FIXME: This is a backward compatibility codepath
-                       if err := json.Unmarshal(sourceWSResponse.Metadata, 
&secrets); err != nil {
-                               return err
-                       }
+               if err != nil {
+                       return err
+               }
+
+               for k, v := range *op.Metadata {
+                       secrets[k] = v.(string)
                }
 
                addresses, err := source.Addresses()
@@ -146,55 +142,23 @@ func copyContainer(config *lxd.Config, sourceResource 
string, destResource strin
                 * course, if all the errors are websocket errors, let's just
                 * report that.
                 */
-               var realError error
-
                for _, addr := range addresses {
                        var migration *lxd.Response
 
                        sourceWSUrl := "https://"; + addr + 
sourceWSResponse.Operation
                        migration, err = dest.MigrateFrom(destName, 
sourceWSUrl, secrets, status.Architecture, status.Config, status.Devices, 
status.Profiles, baseImage, ephemeral == 1)
                        if err != nil {
-                               if !strings.Contains(err.Error(), "websocket: 
bad handshake") {
-                                       realError = err
-                               }
-                               shared.Debugf("intermediate error: %s", err)
                                continue
                        }
 
                        if err = dest.WaitForSuccess(migration.Operation); err 
!= nil {
-                               if !strings.Contains(err.Error(), "websocket: 
bad handshake") {
-                                       realError = err
-                               }
-                               shared.Debugf("intermediate error: %s", err)
-                               // FIXME: This is a backward compatibility 
codepath
-                               sourceWSUrl := "wss://" + addr + 
sourceWSResponse.Operation + "/websocket"
-
-                               migration, err = dest.MigrateFrom(destName, 
sourceWSUrl, secrets, status.Architecture, status.Config, status.Devices, 
status.Profiles, baseImage, ephemeral == 1)
-                               if err != nil {
-                                       if !strings.Contains(err.Error(), 
"websocket: bad handshake") {
-                                               realError = err
-                                       }
-                                       shared.Debugf("intermediate error: %s", 
err)
-                                       continue
-                               }
-
-                               if err = 
dest.WaitForSuccess(migration.Operation); err != nil {
-                                       if !strings.Contains(err.Error(), 
"websocket: bad handshake") {
-                                               realError = err
-                                       }
-                                       shared.Debugf("intermediate error: %s", 
err)
-                                       continue
-                               }
+                               return err
                        }
 
                        return nil
                }
 
-               if realError != nil {
-                       return realError
-               } else {
-                       return err
-               }
+               return err
        }
 }
 
diff --git a/lxc/image.go b/lxc/image.go
index 9ace054..2be6fe9 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -255,8 +255,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) 
error {
                fmt.Printf(i18n.G("Fingerprint: %s")+"\n", info.Fingerprint)
                public := i18n.G("no")
 
-               // FIXME: InterfaceToBool is there for backward compatibility
-               if shared.InterfaceToBool(info) {
+               if info.Public {
                        public = i18n.G("yes")
                }
 
@@ -504,8 +503,7 @@ func showImages(images []shared.ImageInfo, filters 
[]string) error {
                public := i18n.G("no")
                description := findDescription(image.Properties)
 
-               // FIXME: InterfaceToBool is there for backward compatibility
-               if shared.InterfaceToBool(image.Public) {
+               if image.Public {
                        public = i18n.G("yes")
                }
 
diff --git a/lxd/images.go b/lxd/images.go
index c69f8b9..a24de23 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -660,10 +660,7 @@ func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) 
(metadata map[string]s
                info.Fingerprint,
                info.Filename,
                info.Size,
-
-               // FIXME: InterfaceToBool is there for backward compatibility
-               shared.InterfaceToBool(info.Public),
-
+               info.Public,
                info.Architecture,
                info.CreationDate,
                info.ExpiryDate,
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 46f2efd..254ad4d 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -446,20 +446,9 @@ func (c *migrationSink) connectWithSecret(secret string) 
(*websocket.Conn, error
        query := url.Values{"secret": []string{secret}}
 
        // The URL is a https URL to the operation, mangle to be a wss URL to 
the secret
-       url := c.url
-       if strings.HasPrefix(url, "https://";) {
-               url = fmt.Sprintf("wss://%s", strings.TrimPrefix(url, 
"https://";))
-       }
-
-       // FIXME: This is a backward compatibility codepath
-       if !strings.HasSuffix(url, "/websocket") {
-               url = fmt.Sprintf("%s/websocket", url)
-       }
-
-       // Append the secret suffix
-       url = fmt.Sprintf("%s?%s", url, query.Encode())
+       wsUrl := fmt.Sprintf("wss://%s/websocket?%s", strings.TrimPrefix(c.url, 
"https://";), query.Encode())
 
-       return lxd.WebsocketDial(c.dialer, url)
+       return lxd.WebsocketDial(c.dialer, wsUrl)
 }
 
 func (c *migrationSink) do() error {
diff --git a/shared/image.go b/shared/image.go
index 692ed3c..c3d3073 100644
--- a/shared/image.go
+++ b/shared/image.go
@@ -15,14 +15,11 @@ type ImageInfo struct {
        Fingerprint  string            `json:"fingerprint"`
        Filename     string            `json:"filename"`
        Properties   map[string]string `json:"properties"`
-
-       // FIXME: This is an interface{] instead of a bool for backward 
compatibility
-       Public interface{} `json:"public"`
-
-       Size         int64 `json:"size"`
-       CreationDate int64 `json:"created_at"`
-       ExpiryDate   int64 `json:"expires_at"`
-       UploadDate   int64 `json:"uploaded_at"`
+       Public       bool              `json:"public"`
+       Size         int64             `json:"size"`
+       CreationDate int64             `json:"created_at"`
+       ExpiryDate   int64             `json:"expires_at"`
+       UploadDate   int64             `json:"uploaded_at"`
 }
 
 /*
@@ -37,21 +34,16 @@ type BriefImageInfo struct {
 func (i *ImageInfo) BriefInfo() BriefImageInfo {
        retstate := BriefImageInfo{
                Properties: i.Properties,
-
-               // FIXME: InterfaceToBool is there for backward compatibility
-               Public: InterfaceToBool(i.Public)}
+               Public:     i.Public}
        return retstate
 }
 
 type ImageBaseInfo struct {
-       Id          int
-       Fingerprint string
-       Filename    string
-       Size        int64
-
-       // FIXME: This is an interface{] instead of a bool for backward 
compatibility
-       Public interface{}
-
+       Id           int
+       Fingerprint  string
+       Filename     string
+       Size         int64
+       Public       bool
        Architecture int
        CreationDate int64
        ExpiryDate   int64
diff --git a/shared/util.go b/shared/util.go
index 3615789..c3c6a29 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -501,22 +501,6 @@ func ValidHostname(name string) bool {
        return true
 }
 
-// FIXME: InterfaceToBool is there for backward compatibility
-func InterfaceToBool(value interface{}) bool {
-       switch t := value.(type) {
-       case bool:
-               return t
-       case float32:
-               return t == 1
-       case float64:
-               return t == 1
-       case int:
-               return t == 1
-       default:
-               return false
-       }
-}
-
 func TextEditor(inPath string, inContent []byte) ([]byte, error) {
        var f *os.File
        var err error

From 6cec2158e513dade1af838be3ced08b0e8a1da6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 10 Feb 2016 14:08:33 -0500
Subject: [PATCH 2/6] Fail when unsetting a key that's not currentl set
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1477

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxc/config.go | 40 ++++++++++++++++++++++++---
 po/lxd.pot    | 86 +++++++++++++++++++++++++++++++++--------------------------
 2 files changed, 84 insertions(+), 42 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index 19709bb..1f95535 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -90,7 +90,7 @@ To set the server trust password:
     lxc config set core.trust_password blah`)
 }
 
-func doSet(config *lxd.Config, args []string) error {
+func doSet(config *lxd.Config, args []string, unset bool) error {
        if len(args) != 4 {
                return errArgs
        }
@@ -108,11 +108,23 @@ func doSet(config *lxd.Config, args []string) error {
        if !terminal.IsTerminal(int(syscall.Stdin)) && value == "-" {
                buf, err := ioutil.ReadAll(os.Stdin)
                if err != nil {
-                       return fmt.Errorf("Can't read from stdin: %s", err)
+                       return fmt.Errorf(i18n.G("Can't read from stdin: %s"), 
err)
                }
                value = string(buf[:])
        }
 
+       if unset {
+               st, err := d.ContainerStatus(container)
+               if err != nil {
+                       return err
+               }
+
+               _, ok := st.Config[key]
+               if !ok {
+                       return fmt.Errorf(i18n.G("Can't unset key '%s', it's 
not currently set."), key)
+               }
+       }
+
        return d.SetContainerConfig(container, key, value)
 }
 
@@ -135,6 +147,16 @@ func (c *configCmd) run(config *lxd.Config, args []string) 
error {
                                return err
                        }
 
+                       ss, err := c.ServerStatus()
+                       if err != nil {
+                               return err
+                       }
+
+                       _, ok := ss.Config[args[1]]
+                       if !ok {
+                               return fmt.Errorf(i18n.G("Can't unset key '%s', 
it's not currently set."), args[1])
+                       }
+
                        _, err = c.SetServerConfig(args[1], "")
                        return err
                }
@@ -147,13 +169,23 @@ func (c *configCmd) run(config *lxd.Config, args 
[]string) error {
                                return err
                        }
 
+                       ss, err := c.ServerStatus()
+                       if err != nil {
+                               return err
+                       }
+
+                       _, ok := ss.Config[args[1]]
+                       if !ok {
+                               return fmt.Errorf(i18n.G("Can't unset key '%s', 
it's not currently set."), args[1])
+                       }
+
                        _, err = c.SetServerConfig(args[2], "")
                        return err
                }
 
                // Deal with container
                args = append(args, "")
-               return doSet(config, args)
+               return doSet(config, args, true)
 
        case "set":
                if len(args) < 3 {
@@ -184,7 +216,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) 
error {
                }
 
                // Deal with container
-               return doSet(config, args)
+               return doSet(config, args, false)
 
        case "trust":
                if len(args) < 2 {
diff --git a/po/lxd.pot b/po/lxd.pot
index 5baf553..f423d71 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-02-09 15:53-0700\n"
+        "POT-Creation-Date: 2016-02-10 14:08-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
         "Language-Team: LANGUAGE <l...@li.org>\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:501
+#: lxc/image.go:500
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -78,11 +78,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:522 lxc/image.go:544
+#: lxc/image.go:520 lxc/image.go:542
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:526
+#: lxc/image.go:524
 msgid   "ARCH"
 msgstr  ""
 
@@ -95,7 +95,7 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:282
+#: lxc/image.go:281
 msgid   "Aliases:"
 msgstr  ""
 
@@ -103,7 +103,7 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:265
+#: lxc/image.go:264
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
@@ -112,10 +112,20 @@ msgstr  ""
 msgid   "Available commands:"
 msgstr  ""
 
-#: lxc/config.go:232
+#: lxc/config.go:264
 msgid   "COMMON NAME"
 msgstr  ""
 
+#: lxc/config.go:111
+#, c-format
+msgid   "Can't read from stdin: %s"
+msgstr  ""
+
+#: lxc/config.go:124 lxc/config.go:157 lxc/config.go:179
+#, c-format
+msgid   "Can't unset key '%s', it's not currently set."
+msgstr  ""
+
 #: lxc/profile.go:329
 msgid   "Cannot provide container name to list"
 msgstr  ""
@@ -144,7 +154,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:458 lxc/config.go:523 lxc/image.go:599 lxc/profile.go:185
+#: lxc/config.go:490 lxc/config.go:555 lxc/image.go:597 lxc/profile.go:185
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -171,7 +181,7 @@ msgstr  ""
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/copy.go:23
+#: lxc/copy.go:22
 msgid   "Copy containers within or in between lxd instances.\n"
         "\n"
         "lxc copy [remote:]<source container> [remote:]<destination container> 
[--ephemeral|e]"
@@ -198,7 +208,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:270
+#: lxc/image.go:269
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -212,7 +222,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:525
+#: lxc/image.go:523
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -224,12 +234,12 @@ msgid   "Delete containers or container snapshots.\n"
         "Destroy containers or snapshots with any attached data 
(configuration, snapshots, ...)."
 msgstr  ""
 
-#: lxc/config.go:571
+#: lxc/config.go:603
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:599
+#: lxc/config.go:631
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
@@ -238,7 +248,7 @@ msgstr  ""
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:234
+#: lxc/config.go:266
 msgid   "EXPIRY DATE"
 msgstr  ""
 
@@ -254,7 +264,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:136 lxc/init.go:137 
lxc/launch.go:40 lxc/launch.go:41
+#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:136 lxc/init.go:137 
lxc/launch.go:40 lxc/launch.go:41
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -268,16 +278,16 @@ msgid   "Execute the specified command in a container.\n"
         "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] 
[--env EDITOR=/usr/bin/vim]... <command>"
 msgstr  ""
 
-#: lxc/image.go:274
+#: lxc/image.go:273
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:276
+#: lxc/image.go:275
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:231 lxc/image.go:523 lxc/image.go:545
+#: lxc/config.go:263 lxc/image.go:521 lxc/image.go:543
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -316,7 +326,7 @@ msgstr  ""
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:233
+#: lxc/config.go:265
 msgid   "ISSUE DATE"
 msgstr  ""
 
@@ -328,7 +338,7 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:340
+#: lxc/image.go:339
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
@@ -627,15 +637,15 @@ msgstr  ""
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:245
+#: lxc/config.go:277
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:268
+#: lxc/config.go:300
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/image.go:332
+#: lxc/image.go:331
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -643,7 +653,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:426
+#: lxc/image.go:425
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -656,7 +666,7 @@ msgstr  ""
 msgid   "PID"
 msgstr  ""
 
-#: lxc/image.go:524 lxc/remote.go:273
+#: lxc/image.go:522 lxc/remote.go:273
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -682,7 +692,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:459 lxc/config.go:524 lxc/image.go:600
+#: lxc/config.go:491 lxc/config.go:556 lxc/image.go:598
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -733,7 +743,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:278
+#: lxc/image.go:277
 msgid   "Properties:"
 msgstr  ""
 
@@ -741,7 +751,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:266
+#: lxc/image.go:265
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -761,7 +771,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:527
+#: lxc/image.go:525
 msgid   "SIZE"
 msgstr  ""
 
@@ -814,7 +824,7 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/image.go:263
+#: lxc/image.go:262
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -845,7 +855,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
-#: lxc/image.go:267
+#: lxc/image.go:266
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -862,7 +872,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:528
+#: lxc/image.go:526
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -870,7 +880,7 @@ msgstr  ""
 msgid   "URL"
 msgstr  ""
 
-#: lxc/image.go:272
+#: lxc/image.go:271
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -912,7 +922,7 @@ msgstr  ""
 msgid   "bad result type from action"
 msgstr  ""
 
-#: lxc/copy.go:79
+#: lxc/copy.go:78
 msgid   "can't copy to the same container name"
 msgstr  ""
 
@@ -942,11 +952,11 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:256 lxc/image.go:504
+#: lxc/image.go:256 lxc/image.go:503
 msgid   "no"
 msgstr  ""
 
-#: lxc/copy.go:101
+#: lxc/copy.go:100
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
@@ -982,11 +992,11 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/image.go:260 lxc/image.go:509
+#: lxc/image.go:259 lxc/image.go:507
 msgid   "yes"
 msgstr  ""
 
-#: lxc/copy.go:39
+#: lxc/copy.go:38
 msgid   "you must specify a source container name"
 msgstr  ""
 

From 7959797f3fd9698bc4552b8183c47465ff112f9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 10 Feb 2016 14:40:32 -0500
Subject: [PATCH 3/6] Implement interactive mode in delete
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #781

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxc/delete.go            | 44 ++++++++++++++++++++++++++++++++++++--------
 po/lxd.pot               | 29 +++++++++++++++++++++++++----
 test/main.sh             |  2 +-
 test/suites/basic.sh     |  4 ++--
 test/suites/filemanip.sh |  2 +-
 5 files changed, 65 insertions(+), 16 deletions(-)

diff --git a/lxc/delete.go b/lxc/delete.go
index d241aea..d09498d 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -1,14 +1,21 @@
 package main
 
 import (
+       "bufio"
        "fmt"
+       "os"
+       "strings"
 
        "github.com/lxc/lxd"
        "github.com/lxc/lxd/i18n"
        "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/gnuflag"
 )
 
-type deleteCmd struct{}
+type deleteCmd struct {
+       force       bool
+       interactive bool
+}
 
 func (c *deleteCmd) showByDefault() bool {
        return true
@@ -23,9 +30,24 @@ lxc delete [remote:]<container>[/<snapshot>] 
[remote:][<container>[/<snapshot>].
 Destroy containers or snapshots with any attached data (configuration, 
snapshots, ...).`)
 }
 
-func (c *deleteCmd) flags() {}
+func (c *deleteCmd) flags() {
+       gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the removal of 
stopped containers."))
+       gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the removal of 
stopped containers."))
+       gnuflag.BoolVar(&c.interactive, "i", false, i18n.G("Require user 
confirmation."))
+       gnuflag.BoolVar(&c.interactive, "interactive", false, i18n.G("Require 
user confirmation."))
+}
+
+func (c *deleteCmd) doDelete(d *lxd.Client, name string) error {
+       if c.interactive {
+               reader := bufio.NewReader(os.Stdin)
+               fmt.Printf(i18n.G("Remove %s (yes/no): "), name)
+               input, _ := reader.ReadString('\n')
+               input = strings.TrimSuffix(input, "\n")
+               if !shared.StringInSlice(strings.ToLower(input), 
[]string{i18n.G("yes")}) {
+                       return fmt.Errorf(i18n.G("User aborted delete 
operation."))
+               }
+       }
 
-func doDelete(d *lxd.Client, name string) error {
        resp, err := d.Delete(name)
        if err != nil {
                return err
@@ -47,14 +69,20 @@ func (c *deleteCmd) run(config *lxd.Config, args []string) 
error {
                        return err
                }
 
-               ct, err := d.ContainerStatus(name)
+               if shared.IsSnapshot(name) {
+                       return c.doDelete(d, name)
+               }
 
+               ct, err := d.ContainerStatus(name)
                if err != nil {
-                       // Could be a snapshot
-                       return doDelete(d, name)
+                       return err
                }
 
                if ct.Status.StatusCode != 0 && ct.Status.StatusCode != 
shared.Stopped {
+                       if !c.force {
+                               return fmt.Errorf(i18n.G("The container is 
currently running, stop it first or pass --force."))
+                       }
+
                        resp, err := d.Action(name, shared.Stop, -1, true)
                        if err != nil {
                                return err
@@ -73,10 +101,10 @@ func (c *deleteCmd) run(config *lxd.Config, args []string) 
error {
                                return nil
                        }
                }
-               if err := doDelete(d, name); err != nil {
+
+               if err := c.doDelete(d, name); err != nil {
                        return err
                }
        }
        return nil
-
 }
diff --git a/po/lxd.pot b/po/lxd.pot
index f423d71..2afd38d 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-02-10 14:08-0500\n"
+        "POT-Creation-Date: 2016-02-10 14:55-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
         "Language-Team: LANGUAGE <l...@li.org>\n"
@@ -226,7 +226,7 @@ msgstr  ""
 msgid   "DESCRIPTION"
 msgstr  ""
 
-#: lxc/delete.go:18
+#: lxc/delete.go:25
 msgid   "Delete containers or container snapshots.\n"
         "\n"
         "lxc delete [remote:]<container>[/<snapshot>] 
[remote:][<container>[/<snapshot>]...]\n"
@@ -310,6 +310,10 @@ msgstr  ""
 msgid   "Force the container to shutdown."
 msgstr  ""
 
+#: lxc/delete.go:34 lxc/delete.go:35
+msgid   "Force the removal of stopped containers."
+msgstr  ""
+
 #: lxc/main.go:56
 msgid   "Force using the local unix socket."
 msgstr  ""
@@ -766,6 +770,15 @@ msgstr  ""
 msgid   "Remote admin password"
 msgstr  ""
 
+#: lxc/delete.go:43
+#, c-format
+msgid   "Remove %s (yes/no): "
+msgstr  ""
+
+#: lxc/delete.go:36 lxc/delete.go:37
+msgid   "Require user confirmation."
+msgstr  ""
+
 #: lxc/init.go:244
 #, c-format
 msgid   "Retrieving image: %s"
@@ -843,10 +856,14 @@ msgstr  ""
 msgid   "Status: %s"
 msgstr  ""
 
-#: lxc/delete.go:69
+#: lxc/delete.go:97
 msgid   "Stopping container failed!"
 msgstr  ""
 
+#: lxc/delete.go:83
+msgid   "The container is currently running, stop it first or pass --force."
+msgstr  ""
+
 #: lxc/publish.go:57
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
@@ -894,6 +911,10 @@ msgstr  ""
 msgid   "Usage: lxc [subcommand] [options]"
 msgstr  ""
 
+#: lxc/delete.go:47
+msgid   "User aborted delete operation."
+msgstr  ""
+
 #: lxc/restore.go:35
 msgid   "Whether or not to restore the container's running state from snapshot 
(if available)"
 msgstr  ""
@@ -992,7 +1013,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/image.go:259 lxc/image.go:507
+#: lxc/delete.go:46 lxc/image.go:259 lxc/image.go:507
 msgid   "yes"
 msgstr  ""
 
diff --git a/test/main.sh b/test/main.sh
index 55e05d6..e13bd2b 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -183,7 +183,7 @@ kill_lxd() {
     # Delete all containers
     echo "==> Deleting all containers"
     for container in $(lxc list --force-local | tail -n+3 | grep "^| " | cut 
-d' ' -f2); do
-      lxc delete "${container}" --force-local || true
+      lxc delete "${container}" --force-local -f || true
     done
 
     # Delete all images
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 6b55f13..0a1e31a 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -211,10 +211,10 @@ test_basic_usage() {
 
   lxc launch testimage deleterunning
   my_curl -X DELETE "https://${LXD_ADDR}/1.0/containers/deleterunning"; | grep 
"container is running"
-  lxc delete deleterunning
+  lxc delete deleterunning -f
 
   # cleanup
-  lxc delete foo
+  lxc delete foo -f
 
   # check that an apparmor profile is created for this container, that it is
   # unloaded on stop, and that it is deleted when the container is deleted
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index a8c2cb8..dd2317c 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -10,5 +10,5 @@ test_filemanip() {
   [ ! -f /tmp/main.sh ]
   [ -f "${LXD_DIR}/containers/filemanip/rootfs/tmp/main.sh" ]
 
-  lxc delete filemanip
+  lxc delete filemanip -f
 }

From 21d3b9b3dfe4c98ecff990fd2fe7e29d36404bb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 10 Feb 2016 15:15:10 -0500
Subject: [PATCH 4/6] specs: Re-sync database spec with reality
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 specs/database.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/specs/database.md b/specs/database.md
index 6f5162d..2632fd5 100644
--- a/specs/database.md
+++ b/specs/database.md
@@ -109,8 +109,8 @@ id              | INTEGER       | SERIAL        | NOT NULL  
        | SERIAL
 name            | VARCHAR(255)  | -             | NOT NULL          | 
Container name
 architecture    | INTEGER       | -             | NOT NULL          | 
Container architecture
 type            | INTEGER       | 0             | NOT NULL          | 
Container type (0 = container, 1 = container snapshot)
-power\_state    | INTEGER       | 0             | NOT NULL          | 
Container power state (0 = off, 1 = on)
 ephemeral       | INTEGER       | 0             | NOT NULL          | Whether 
the container is ephemeral (0 = persistent, 1 = ephemeral)
+creation\_date  | DATETIME      | -             |                   | Image 
creation date (user supplied, 0 = unknown)
 
 Index: UNIQUE ON id AND name
 
@@ -190,31 +190,31 @@ last\_use\_date | DATETIME      | -             |         
          | Last time
 Index: UNIQUE ON id AND fingerprint
 
 
-## images\_properties
+## images\_aliases
 
 Column          | Type          | Default       | Constraint        | 
Description
 :-----          | :---          | :------       | :---------        | 
:----------
 id              | INTEGER       | SERIAL        | NOT NULL          | SERIAL
+name            | VARCHAR(255)  | -             | NOT NULL          | Alias 
name
 image\_id       | INTEGER       | -             | NOT NULL          | 
images.id FK
-type            | INTEGER       | 0             | NOT NULL          | Property 
type (0 = string, 1 = text)
-key             | VARCHAR(255)  | -             | NOT NULL          | Property 
name
-value           | TEXT          | -             |                   | Property 
value (NULL for unset)
+description     | VARCHAR(255)  | -             |                   | 
Description of the alias
 
-Index: UNIQUE ON id
+Index: UNIQUE ON id AND name
 
 Foreign keys: image\_id REFERENCES images(id)
 
 
-## images\_aliases
+## images\_properties
 
 Column          | Type          | Default       | Constraint        | 
Description
 :-----          | :---          | :------       | :---------        | 
:----------
 id              | INTEGER       | SERIAL        | NOT NULL          | SERIAL
-name            | VARCHAR(255)  | -             | NOT NULL          | Alias 
name
 image\_id       | INTEGER       | -             | NOT NULL          | 
images.id FK
-description     | VARCHAR(255)  | -             |                   | 
Description of the alias
+type            | INTEGER       | 0             | NOT NULL          | Property 
type (0 = string, 1 = text)
+key             | VARCHAR(255)  | -             | NOT NULL          | Property 
name
+value           | TEXT          | -             |                   | Property 
value (NULL for unset)
 
-Index: UNIQUE ON id AND name
+Index: UNIQUE ON id
 
 Foreign keys: image\_id REFERENCES images(id)
 

From e63445b6f9acbd14d4aab29278724b1c0401e388 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 10 Feb 2016 16:00:59 -0500
Subject: [PATCH 5/6] Record a creation time for containers and snapshots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/container.go          |  2 ++
 lxd/container_lxc.go      |  7 +++++++
 lxd/container_snapshot.go | 27 ++++++++++++---------------
 lxd/db.go                 |  3 ++-
 lxd/db_containers.go      | 12 ++++++++----
 lxd/db_images.go          |  4 ++--
 lxd/db_update.go          | 14 ++++++++++++++
 shared/container.go       |  7 +++++++
 specs/rest-api.md         |  3 +++
 test/suites/basic.sh      |  2 +-
 10 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 87dd19d..272b7f2 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -296,6 +296,7 @@ type containerArgs struct {
        Architecture int
        BaseImage    string
        Config       map[string]string
+       CreationDate *time.Time
        Ctype        containerType
        Devices      shared.Devices
        Ephemeral    bool
@@ -350,6 +351,7 @@ type container interface {
        Id() int
        Name() string
        Architecture() int
+       CreationDate() *time.Time
        ExpandedConfig() map[string]string
        ExpandedDevices() shared.Devices
        LocalConfig() map[string]string
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 073b9b7..3a22f74 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -86,6 +86,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) 
(container, error) {
                ephemeral:    args.Ephemeral,
                architecture: args.Architecture,
                cType:        args.Ctype,
+               creationDate: args.CreationDate,
                profiles:     args.Profiles,
                localConfig:  args.Config,
                localDevices: args.Devices}
@@ -181,6 +182,7 @@ func containerLXCLoad(d *Daemon, args containerArgs) 
(container, error) {
                ephemeral:    args.Ephemeral,
                architecture: args.Architecture,
                cType:        args.Ctype,
+               creationDate: args.CreationDate,
                profiles:     args.Profiles,
                localConfig:  args.Config,
                localDevices: args.Devices}
@@ -206,6 +208,7 @@ type containerLXC struct {
        // Properties
        architecture int
        cType        containerType
+       creationDate *time.Time
        ephemeral    bool
        id           int
        name         string
@@ -1362,6 +1365,7 @@ func (c *containerLXC) RenderState() 
(*shared.ContainerState, error) {
        return &shared.ContainerState{
                Architecture:    c.architecture,
                Config:          c.localConfig,
+               CreationDate:    c.creationDate.Unix(),
                Devices:         c.localDevices,
                Ephemeral:       c.ephemeral,
                ExpandedConfig:  c.expandedConfig,
@@ -3825,6 +3829,9 @@ func (c *containerLXC) Architecture() int {
        return c.architecture
 }
 
+func (c *containerLXC) CreationDate() *time.Time {
+       return c.creationDate
+}
 func (c *containerLXC) ExpandedConfig() map[string]string {
        return c.expandedConfig
 }
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index dfff877..063364e 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -10,8 +10,6 @@ import (
        "github.com/gorilla/mux"
 
        "github.com/lxc/lxd/shared"
-
-       log "gopkg.in/inconshreveable/log15.v2"
 )
 
 func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
@@ -22,13 +20,12 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) 
Response {
        }
 
        cname := mux.Vars(r)["name"]
-       // Makes sure the requested container exists.
-       _, err = containerLoadByName(d, cname)
+       c, err := containerLoadByName(d, cname)
        if err != nil {
                return SmartError(err)
        }
 
-       results, err := dbContainerGetSnapshots(d.db, cname)
+       snaps, err := c.Snapshots()
        if err != nil {
                return SmartError(err)
        }
@@ -36,19 +33,16 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) 
Response {
        resultString := []string{}
        resultMap := []shared.Jmap{}
 
-       for _, name := range results {
-               sc, err := containerLoadByName(d, name)
-               if err != nil {
-                       shared.Log.Error("Failed to load snapshot", 
log.Ctx{"snapshot": name})
-                       continue
-               }
-
-               snapName := strings.SplitN(name, shared.SnapshotDelimiter, 2)[1]
+       for _, snap := range snaps {
+               snapName := strings.SplitN(snap.Name(), 
shared.SnapshotDelimiter, 2)[1]
                if recursion == 0 {
                        url := fmt.Sprintf("/%s/containers/%s/snapshots/%s", 
shared.APIVersion, cname, snapName)
                        resultString = append(resultString, url)
                } else {
-                       body := shared.Jmap{"name": snapName, "stateful": 
shared.PathExists(sc.StatePath())}
+                       body := shared.Jmap{
+                               "name":          snapName,
+                               "creation_date": snap.CreationDate().Unix(),
+                               "stateful":      
shared.PathExists(snap.StatePath())}
                        resultMap = append(resultMap, body)
                }
        }
@@ -189,7 +183,10 @@ func snapshotHandler(d *Daemon, r *http.Request) Response {
 }
 
 func snapshotGet(sc container, name string) Response {
-       body := shared.Jmap{"name": name, "stateful": 
shared.PathExists(sc.StatePath())}
+       body := shared.Jmap{
+               "name":          name,
+               "creation_date": sc.CreationDate().Unix(),
+               "stateful":      shared.PathExists(sc.StatePath())}
        return SyncResponse(true, body)
 }
 
diff --git a/lxd/db.go b/lxd/db.go
index 9b5606c..646d076 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,7 +34,7 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 21
+const DB_CURRENT_VERSION int = 22
 
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
@@ -58,6 +58,7 @@ CREATE TABLE IF NOT EXISTS containers (
     architecture INTEGER NOT NULL,
     type INTEGER NOT NULL,
     ephemeral INTEGER NOT NULL DEFAULT 0,
+    creation_date DATETIME,
     UNIQUE (name)
 );
 CREATE TABLE IF NOT EXISTS containers_config (
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index a1092e8..c8a1e53 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -3,6 +3,7 @@ package main
 import (
        "database/sql"
        "fmt"
+       "time"
 
        "github.com/lxc/lxd/shared"
 
@@ -65,9 +66,9 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, 
error) {
        args.Name = name
 
        ephemInt := -1
-       q := "SELECT id, architecture, type, ephemeral FROM containers WHERE 
name=?"
+       q := "SELECT id, architecture, type, ephemeral, creation_date FROM 
containers WHERE name=?"
        arg1 := []interface{}{name}
-       arg2 := []interface{}{&args.Id, &args.Architecture, &args.Ctype, 
&ephemInt}
+       arg2 := []interface{}{&args.Id, &args.Architecture, &args.Ctype, 
&ephemInt, &args.CreationDate}
        err := dbQueryRowScan(db, q, arg1, arg2)
        if err != nil {
                return args, err
@@ -123,14 +124,17 @@ func dbContainerCreate(db *sql.DB, args containerArgs) 
(int, error) {
                ephemInt = 1
        }
 
-       str := fmt.Sprintf("INSERT INTO containers (name, architecture, type, 
ephemeral) VALUES (?, ?, ?, ?)")
+       now := time.Now().UTC()
+       args.CreationDate = &now
+
+       str := fmt.Sprintf("INSERT INTO containers (name, architecture, type, 
ephemeral, creation_date) VALUES (?, ?, ?, ?, ?)")
        stmt, err := tx.Prepare(str)
        if err != nil {
                tx.Rollback()
                return 0, err
        }
        defer stmt.Close()
-       result, err := stmt.Exec(args.Name, args.Architecture, args.Ctype, 
ephemInt)
+       result, err := stmt.Exec(args.Name, args.Architecture, args.Ctype, 
ephemInt, args.CreationDate.Unix())
        if err != nil {
                tx.Rollback()
                return 0, err
diff --git a/lxd/db_images.go b/lxd/db_images.go
index 3f42e2b..2cd41c7 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -150,9 +150,9 @@ func dbImageSetPublic(db *sql.DB, id int, public bool) 
error {
        return err
 }
 
-// Insert an alias into the database.
+// Insert an alias ento the database.
 func dbImageAliasAdd(db *sql.DB, name string, imageID int, desc string) error {
-       stmt := `INSERT into images_aliases (name, image_id, description) 
values (?, ?, ?)`
+       stmt := `INSERT INTO images_aliases (name, image_id, description) 
values (?, ?, ?)`
        _, err := dbExec(db, stmt, name, imageID, desc)
        return err
 }
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 6a7a8fe..0e39f84 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -15,6 +15,14 @@ import (
        log "gopkg.in/inconshreveable/log15.v2"
 )
 
+func dbUpdateFromV21(db *sql.DB) error {
+       stmt := `
+ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;
+INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+       _, err := db.Exec(stmt, 22)
+       return err
+}
+
 func dbUpdateFromV20(d *Daemon) error {
        cNames, err := dbContainersList(d.db, cTypeRegular)
        if err != nil {
@@ -900,6 +908,12 @@ func dbUpdate(d *Daemon, prevVersion int) error {
                        return err
                }
        }
+       if prevVersion < 22 {
+               err = dbUpdateFromV21(db)
+               if err != nil {
+                       return err
+               }
+       }
 
        return nil
 }
diff --git a/shared/container.go b/shared/container.go
index cb75ec3..d4bb6da 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -24,9 +24,16 @@ type ContainerExecControl struct {
        Args    map[string]string `json:"args"`
 }
 
+type SnapshotState struct {
+       CreationDate int64  `json:"creation_date"`
+       Name         string `json:"name"`
+       Stateful     bool   `json:"stateful"`
+}
+
 type ContainerState struct {
        Architecture    int               `json:"architecture"`
        Config          map[string]string `json:"config"`
+       CreationDate    int64             `json:"creation_date"`
        Devices         Devices           `json:"devices"`
        Ephemeral       bool              `json:"ephemeral"`
        ExpandedConfig  map[string]string `json:"expanded_config"`
diff --git a/specs/rest-api.md b/specs/rest-api.md
index 017ece0..0ca83db 100644
--- a/specs/rest-api.md
+++ b/specs/rest-api.md
@@ -385,6 +385,8 @@ Output:
         'profiles': ["default"],
         'architecture': 2,
         'config': {"limits.cpu": "3"},
+        'ephemeral': false,
+        'creation_date': 1455136027,
         'expanded_config': {"limits.cpu": "3"}  # the result of expanding 
profiles and adding the container's local config
         'devices': {
             'rootfs': {
@@ -572,6 +574,7 @@ Input:
 Return:
 
     {
+        'creation_date': 1455139453,
         'name': "my-snapshot",
         'stateful': true
     }
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 0a1e31a..c24d53a 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -44,7 +44,7 @@ test_basic_usage() {
 
   # Test container creation
   lxc init testimage foo
-  lxc list | grep foo | grep STOPPED
+  lxc list | grep foo | grep STOPPED || bash
   lxc list fo | grep foo | grep STOPPED
 
   # Test container rename

From 87408f9b9c49fda54df4a494e8d5fdb7f1d60e03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 10 Feb 2016 16:01:16 -0500
Subject: [PATCH 6/6] Rework snapshot rendering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Don't print the awkward list in "lxc list" anymore.
 - Update "lxc info" to show:
   - Snapshot name
   - Snapshot creation date (if known)
   - Snapshot type (stateful or stateless)

Closes #1560

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 client.go   | 16 +++-------------
 lxc/info.go | 22 +++++++++++++++++++++-
 lxc/list.go | 15 ++-------------
 po/lxd.pot  | 61 +++++++++++++++++++++++++++++++++++++------------------------
 4 files changed, 63 insertions(+), 51 deletions(-)

diff --git a/client.go b/client.go
index 136b96c..45eb1a2 100644
--- a/client.go
+++ b/client.go
@@ -1624,30 +1624,20 @@ 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) ([]string, error) {
+func (c *Client) ListSnapshots(container string) ([]shared.SnapshotState, 
error) {
        qUrl := fmt.Sprintf("containers/%s/snapshots?recursion=1", container)
        resp, err := c.get(qUrl)
        if err != nil {
                return nil, err
        }
 
-       var result []shared.Jmap
+       var result []shared.SnapshotState
 
        if err := json.Unmarshal(resp.Metadata, &result); err != nil {
                return nil, err
        }
 
-       names := []string{}
-
-       for _, snapjmap := range result {
-               name, err := snapjmap.GetString("name")
-               if err != nil {
-                       continue
-               }
-               names = append(names, name)
-       }
-
-       return names, nil
+       return result, nil
 }
 
 func (c *Client) GetServerConfigString() ([]string, error) {
diff --git a/lxc/info.go b/lxc/info.go
index 6116341..ba644e1 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -4,6 +4,7 @@ import (
        "fmt"
        "io/ioutil"
        "strings"
+       "time"
 
        "gopkg.in/yaml.v2"
 
@@ -76,7 +77,13 @@ func containerInfo(d *lxd.Client, name string, showLog bool) 
error {
                return err
        }
 
+       const layout = "2006/01/02 15:04 UTC"
+
        fmt.Printf(i18n.G("Name: %s")+"\n", ct.Name)
+       if ct.CreationDate != 0 {
+               fmt.Printf(i18n.G("Created: %s")+"\n", 
time.Unix(ct.CreationDate, 0).UTC().Format(layout))
+       }
+
        fmt.Printf(i18n.G("Status: %s")+"\n", ct.Status.Status)
        if ct.Ephemeral {
                fmt.Printf(i18n.G("Type: ephemeral") + "\n")
@@ -109,11 +116,24 @@ func containerInfo(d *lxd.Client, name string, showLog 
bool) error {
        if err != nil {
                return nil
        }
+
        for _, snap := range snaps {
                if first_snapshot {
                        fmt.Println(i18n.G("Snapshots:"))
                }
-               fmt.Printf("  %s\n", snap)
+               fmt.Printf("  %s", snap.Name)
+
+               if snap.CreationDate != 0 {
+                       fmt.Printf(" ("+i18n.G("taken at %s")+")", 
time.Unix(snap.CreationDate, 0).UTC().Format(layout))
+               }
+
+               if snap.Stateful {
+                       fmt.Printf(" (" + i18n.G("stateful") + ")")
+               } else {
+                       fmt.Printf(" (" + i18n.G("stateless") + ")")
+               }
+               fmt.Printf("\n")
+
                first_snapshot = false
        }
 
diff --git a/lxc/list.go b/lxc/list.go
index 349aeef..ee25e73 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -149,7 +149,7 @@ func shouldShow(filters []string, state 
*shared.ContainerState) bool {
        return true
 }
 
-func listContainers(cinfos []shared.ContainerInfo, filters []string, columns 
[]Column, listsnaps bool) error {
+func listContainers(cinfos []shared.ContainerInfo, filters []string, columns 
[]Column) error {
        headers := []string{}
        for _, column := range columns {
                headers = append(headers, column.Name)
@@ -175,17 +175,6 @@ func listContainers(cinfos []shared.ContainerInfo, filters 
[]string, columns []C
        table.AppendBulk(data)
        table.Render()
 
-       if listsnaps && len(cinfos) == 1 {
-               csnaps := cinfos[0].Snaps
-               first_snapshot := true
-               for _, snap := range csnaps {
-                       if first_snapshot {
-                               fmt.Println(i18n.G("Snapshots:"))
-                       }
-                       fmt.Printf("  %s\n", snap)
-                       first_snapshot = false
-               }
-       }
        return nil
 }
 
@@ -250,7 +239,7 @@ func (c *listCmd) run(config *lxd.Config, args []string) 
error {
                }
        }
 
-       return listContainers(cts, filters, columns, len(cts) == 1)
+       return listContainers(cts, filters, columns)
 }
 
 func nameColumnData(cinfo shared.ContainerInfo) string {
diff --git a/po/lxd.pot b/po/lxd.pot
index 2afd38d..07753b4 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-02-10 14:55-0500\n"
+        "POT-Creation-Date: 2016-02-10 16:04-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
         "Language-Team: LANGUAGE <l...@li.org>\n"
@@ -74,7 +74,7 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/info.go:102 lxc/profile.go:221
+#: lxc/info.go:109 lxc/profile.go:221
 msgid   "(none)"
 msgstr  ""
 
@@ -208,7 +208,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:269
+#: lxc/image.go:269 lxc/info.go:84
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -244,7 +244,7 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/list.go:239
+#: lxc/list.go:228
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -322,11 +322,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:237
+#: lxc/list.go:226
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:238
+#: lxc/list.go:227
 msgid   "IPV6"
 msgstr  ""
 
@@ -347,7 +347,7 @@ msgstr  ""
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/info.go:88
+#: lxc/info.go:95
 #, c-format
 msgid   "Init: %d"
 msgstr  ""
@@ -380,7 +380,7 @@ msgstr  ""
 msgid   "Invalid target %s"
 msgstr  ""
 
-#: lxc/info.go:90
+#: lxc/info.go:97
 msgid   "Ips:"
 msgstr  ""
 
@@ -402,7 +402,7 @@ msgid   "Launch a container from a particular image.\n"
         "lxc launch ubuntu u1"
 msgstr  ""
 
-#: lxc/info.go:24
+#: lxc/info.go:25
 msgid   "List information on containers.\n"
         "\n"
         "This will support remotes and images as well, but only containers for 
now.\n"
@@ -433,7 +433,7 @@ msgid   "Lists the available resources.\n"
         "* p - pid of container init process"
 msgstr  ""
 
-#: lxc/info.go:131
+#: lxc/info.go:151
 msgid   "Log:"
 msgstr  ""
 
@@ -624,15 +624,15 @@ msgid   "Move containers within or in between lxd 
instances.\n"
         "    Rename a local container.\n"
 msgstr  ""
 
-#: lxc/list.go:235 lxc/remote.go:271
+#: lxc/list.go:224 lxc/remote.go:271
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/list.go:304 lxc/remote.go:257
+#: lxc/list.go:293 lxc/remote.go:257
 msgid   "NO"
 msgstr  ""
 
-#: lxc/info.go:79
+#: lxc/info.go:82
 #, c-format
 msgid   "Name: %s"
 msgstr  ""
@@ -666,7 +666,7 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:241
+#: lxc/list.go:230
 msgid   "PID"
 msgstr  ""
 
@@ -718,7 +718,7 @@ msgid   "Prints the version number of LXD.\n"
         "lxc version"
 msgstr  ""
 
-#: lxc/info.go:89
+#: lxc/info.go:96
 #, c-format
 msgid   "Processcount: %d"
 msgstr  ""
@@ -742,7 +742,7 @@ msgstr  ""
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
-#: lxc/info.go:86
+#: lxc/info.go:93
 #, c-format
 msgid   "Profiles: %s"
 msgstr  ""
@@ -788,11 +788,11 @@ msgstr  ""
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:240
+#: lxc/list.go:229
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:236
+#: lxc/list.go:225
 msgid   "STATE"
 msgstr  ""
 
@@ -833,7 +833,7 @@ msgstr  ""
 msgid   "Show all commands (not just interesting ones)"
 msgstr  ""
 
-#: lxc/info.go:33
+#: lxc/info.go:34
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
@@ -842,7 +842,7 @@ msgstr  ""
 msgid   "Size: %.2fMB"
 msgstr  ""
 
-#: lxc/info.go:114 lxc/list.go:183
+#: lxc/info.go:122
 msgid   "Snapshots:"
 msgstr  ""
 
@@ -851,7 +851,7 @@ msgstr  ""
 msgid   "Starting %s"
 msgstr  ""
 
-#: lxc/info.go:80
+#: lxc/info.go:87
 #, c-format
 msgid   "Status: %s"
 msgstr  ""
@@ -881,11 +881,11 @@ msgstr  ""
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
 
-#: lxc/info.go:82
+#: lxc/info.go:89
 msgid   "Type: ephemeral"
 msgstr  ""
 
-#: lxc/info.go:84
+#: lxc/info.go:91
 msgid   "Type: persistent"
 msgstr  ""
 
@@ -927,7 +927,7 @@ msgstr  ""
 msgid   "Whether to show the expanded configuration"
 msgstr  ""
 
-#: lxc/list.go:302 lxc/remote.go:259
+#: lxc/list.go:291 lxc/remote.go:259
 msgid   "YES"
 msgstr  ""
 
@@ -1005,6 +1005,19 @@ msgstr  ""
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
+#: lxc/info.go:131
+msgid   "stateful"
+msgstr  ""
+
+#: lxc/info.go:133
+msgid   "stateless"
+msgstr  ""
+
+#: lxc/info.go:127
+#, c-format
+msgid   "taken at %s"
+msgstr  ""
+
 #: lxc/exec.go:158
 msgid   "unreachable return reached"
 msgstr  ""
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to