[lxc-devel] [lxd/master] Bugfixes and image aliases

stgraber on Github lxc-bot at linuxcontainers.org
Wed Mar 8 06:58:01 UTC 2017


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/20170308/cfb7c2d8/attachment.bin>
-------------- next part --------------
From 6f3d99613ec5ff412c83eca6e7c022cb4ac9d2c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:34:21 -0500
Subject: [PATCH 1/4] images: Refactor code a bit
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>
---
 lxd/daemon_images.go |   6 +-
 lxd/images.go        | 212 ++++++++++++++++++++++-----------------------------
 2 files changed, 97 insertions(+), 121 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index b2bd825..9e9d164 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -371,7 +371,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			}
 		}
 
-		_, err = imageBuildFromInfo(d, info)
+		// Create the database entry
+		err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return "", err
 		}
@@ -546,7 +547,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
-	_, err = imageBuildFromInfo(d, &info)
+	// Create the database entry
+	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
 		shared.LogError(
 			"Failed to create image",
diff --git a/lxd/images.go b/lxd/images.go
index c203289..06ac683 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -221,27 +221,26 @@ 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 api.ImagesPost,
-	builddir string) (info api.Image, err error) {
-
+func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, builddir string) (*api.Image, error) {
+	info := api.Image{}
 	info.Properties = map[string]string{}
 	name := req.Source["name"]
 	ctype := req.Source["type"]
 	if ctype == "" || name == "" {
-		return info, fmt.Errorf("No source provided")
+		return nil, fmt.Errorf("No source provided")
 	}
 
 	switch ctype {
 	case "snapshot":
 		if !shared.IsSnapshot(name) {
-			return info, fmt.Errorf("Not a snapshot")
+			return nil, fmt.Errorf("Not a snapshot")
 		}
 	case "container":
 		if shared.IsSnapshot(name) {
-			return info, fmt.Errorf("This is a snapshot")
+			return nil, fmt.Errorf("This is a snapshot")
 		}
 	default:
-		return info, fmt.Errorf("Bad type")
+		return nil, fmt.Errorf("Bad type")
 	}
 
 	info.Filename = req.Filename
@@ -254,19 +253,19 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
 
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return info, err
+		return nil, err
 	}
 
 	// Build the actual image file
 	tarfile, err := ioutil.TempFile(builddir, "lxd_build_tar_")
 	if err != nil {
-		return info, err
+		return nil, err
 	}
 	defer os.Remove(tarfile.Name())
 
 	if err := c.Export(tarfile, req.Properties); err != nil {
 		tarfile.Close()
-		return info, err
+		return nil, err
 	}
 	tarfile.Close()
 
@@ -282,7 +281,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
 	if compress != "none" {
 		compressedPath, err = compressFile(tarfile.Name(), compress)
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 	} else {
 		compressedPath = tarfile.Name()
@@ -292,34 +291,42 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
 	sha256 := sha256.New()
 	tarf, err := os.Open(compressedPath)
 	if err != nil {
-		return info, err
+		return nil, err
 	}
+
 	info.Size, err = io.Copy(sha256, tarf)
 	tarf.Close()
 	if err != nil {
-		return info, err
+		return nil, err
 	}
+
 	info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil))
 
 	_, _, err = dbImageGet(d.db, info.Fingerprint, false, true)
 	if err == nil {
-		return info, fmt.Errorf("The image already exists: %s", info.Fingerprint)
+		return nil, fmt.Errorf("The image already exists: %s", info.Fingerprint)
 	}
 
 	/* rename the the file to the expected name so our caller can use it */
 	finalName := shared.VarPath("images", info.Fingerprint)
 	err = shared.FileMove(compressedPath, finalName)
 	if err != nil {
-		return info, err
+		return nil, err
 	}
 
 	info.Architecture, _ = osarch.ArchitectureName(c.Architecture())
 	info.Properties = req.Properties
 
-	return info, nil
+	// Create the database entry
+	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	if err != nil {
+		return nil, err
+	}
+
+	return &info, nil
 }
 
-func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
+func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, error) {
 	var err error
 	var hash string
 
@@ -328,17 +335,17 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	} else if req.Source["alias"] != "" {
 		hash = req.Source["alias"]
 	} else {
-		return fmt.Errorf("must specify one of alias or fingerprint for init from image")
+		return nil, fmt.Errorf("must specify one of alias or fingerprint for init from image")
 	}
 
 	hash, err = d.ImageDownload(op, req.Source["server"], req.Source["protocol"], req.Source["certificate"], req.Source["secret"], hash, false, req.AutoUpdate, "")
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	id, info, err := dbImageGet(d.db, hash, false, false)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Allow overriding or adding properties
@@ -350,34 +357,29 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, 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.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
-	metadata := make(map[string]string)
-	metadata["fingerprint"] = info.Fingerprint
-	metadata["size"] = strconv.FormatInt(info.Size, 10)
-	op.UpdateMetadata(metadata)
-
-	return nil
+	return info, nil
 }
 
-func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
+func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, error) {
 	var err error
 
 	if req.Source["url"] == "" {
-		return fmt.Errorf("Missing URL")
+		return nil, fmt.Errorf("Missing URL")
 	}
 
 	myhttp, err := d.httpClient("")
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Resolve the image URL
 	head, err := http.NewRequest("HEAD", req.Source["url"], nil)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	architecturesStr := []string{}
@@ -391,28 +393,28 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 
 	raw, err := myhttp.Do(head)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	hash := raw.Header.Get("LXD-Image-Hash")
 	if hash == "" {
-		return fmt.Errorf("Missing LXD-Image-Hash header")
+		return nil, fmt.Errorf("Missing LXD-Image-Hash header")
 	}
 
 	url := raw.Header.Get("LXD-Image-URL")
 	if url == "" {
-		return fmt.Errorf("Missing LXD-Image-URL header")
+		return nil, fmt.Errorf("Missing LXD-Image-URL header")
 	}
 
 	// Import the image
 	hash, err = d.ImageDownload(op, url, "direct", "", "", hash, false, req.AutoUpdate, "")
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	id, info, err := dbImageGet(d.db, hash, false, false)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Allow overriding or adding properties
@@ -423,21 +425,15 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, 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.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
-	metadata := make(map[string]string)
-	metadata["fingerprint"] = info.Fingerprint
-	metadata["size"] = strconv.FormatInt(info.Size, 10)
-	op.UpdateMetadata(metadata)
-
-	return nil
+	return info, nil
 }
 
-func getImgPostInfo(d *Daemon, r *http.Request,
-	builddir string, post *os.File) (info api.Image, err error) {
-
+func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File) (*api.Image, error) {
+	info := api.Image{}
 	var imageMeta *imageMetadata
 	logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"})
 
@@ -456,7 +452,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		// Create a temporary file for the image tarball
 		imageTarf, err := ioutil.TempFile(builddir, "lxd_tar_")
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 		defer os.Remove(imageTarf.Name())
 
@@ -467,11 +463,11 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		// Get the metadata tarball
 		part, err := mr.NextPart()
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 
 		if part.FormName() != "metadata" {
-			return info, fmt.Errorf("Invalid multipart image")
+			return nil, fmt.Errorf("Invalid multipart image")
 		}
 
 		size, err = io.Copy(io.MultiWriter(imageTarf, sha256), part)
@@ -482,7 +478,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to copy the image tarfile",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		// Get the rootfs tarball
@@ -491,20 +487,20 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to get the next part",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		if part.FormName() != "rootfs" {
 			logger.Error(
 				"Invalid multipart image")
 
-			return info, fmt.Errorf("Invalid multipart image")
+			return nil, fmt.Errorf("Invalid multipart image")
 		}
 
 		// Create a temporary file for the rootfs tarball
 		rootfsTarf, err := ioutil.TempFile(builddir, "lxd_tar_")
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 		defer os.Remove(rootfsTarf.Name())
 
@@ -516,7 +512,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to copy the rootfs tarfile",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		info.Filename = part.FileName()
@@ -525,7 +521,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		expectedFingerprint := r.Header.Get("X-LXD-fingerprint")
 		if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint {
 			err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint)
-			return info, err
+			return nil, err
 		}
 
 		imageMeta, err = getImageMetadata(imageTarf.Name())
@@ -533,7 +529,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to get image metadata",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		imgfname := shared.VarPath("images", info.Fingerprint)
@@ -545,7 +541,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 					"err":    err,
 					"source": imageTarf.Name(),
 					"dest":   imgfname})
-			return info, err
+			return nil, err
 		}
 
 		rootfsfname := shared.VarPath("images", info.Fingerprint+".rootfs")
@@ -557,7 +553,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 					"err":    err,
 					"source": rootfsTarf.Name(),
 					"dest":   imgfname})
-			return info, err
+			return nil, err
 		}
 	} else {
 		post.Seek(0, 0)
@@ -568,7 +564,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to copy the tarfile",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		info.Filename = r.Header.Get("X-LXD-filename")
@@ -585,7 +581,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 				"fingerprints don't match, got %s expected %s",
 				info.Fingerprint,
 				expectedFingerprint)
-			return info, err
+			return nil, err
 		}
 
 		imageMeta, err = getImageMetadata(post.Name())
@@ -593,7 +589,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to get image metadata",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		imgfname := shared.VarPath("images", info.Fingerprint)
@@ -605,7 +601,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 					"err":    err,
 					"source": post.Name(),
 					"dest":   imgfname})
-			return info, err
+			return nil, err
 		}
 	}
 
@@ -623,7 +619,13 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		}
 	}
 
-	return info, nil
+	// Create the database entry
+	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	if err != nil {
+		return nil, err
+	}
+
+	return &info, nil
 }
 
 // imageCreateInPool() creates a new storage volume in a given storage pool for
@@ -651,29 +653,6 @@ func imageCreateInPool(d *Daemon, info *api.Image, storagePool string) error {
 	return nil
 }
 
-func imageBuildFromInfo(d *Daemon, info *api.Image) (metadata map[string]string, err error) {
-	err = dbImageInsert(
-		d.db,
-		info.Fingerprint,
-		info.Filename,
-		info.Size,
-		info.Public,
-		info.AutoUpdate,
-		info.Architecture,
-		info.CreatedAt,
-		info.ExpiresAt,
-		info.Properties)
-	if err != nil {
-		return metadata, err
-	}
-
-	metadata = make(map[string]string)
-	metadata["fingerprint"] = info.Fingerprint
-	metadata["size"] = strconv.FormatInt(info.Size, 10)
-
-	return metadata, nil
-}
-
 func imagesPost(d *Daemon, r *http.Request) Response {
 	var err error
 
@@ -727,51 +706,46 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 
 	// Begin background operation
 	run := func(op *operation) error {
-		var info api.Image
+		var info *api.Image
 
 		// Setup the cleanup function
 		defer cleanup(builddir, post)
 
-		/* Processing image copy from remote */
-		if !imageUpload && req.Source["type"] == "image" {
-			err := imgPostRemoteInfo(d, req, op)
-			if err != nil {
-				return err
-			}
-			return nil
-		}
-
-		/* Processing image copy from URL */
-		if !imageUpload && req.Source["type"] == "url" {
-			err := imgPostURLInfo(d, req, op)
-			if err != nil {
-				return err
+		if !imageUpload {
+			if req.Source["type"] == "image" {
+				/* Processing image copy from remote */
+				info, err = imgPostRemoteInfo(d, req, op)
+				if err != nil {
+					return err
+				}
+			} else if req.Source["type"] == "url" {
+				/* Processing image copy from URL */
+				info, err = imgPostURLInfo(d, req, op)
+				if err != nil {
+					return err
+				}
+			} else {
+				/* Processing image creation from container */
+				imagePublishLock.Lock()
+				info, err = imgPostContInfo(d, r, req, builddir)
+				if err != nil {
+					imagePublishLock.Unlock()
+					return err
+				}
+				imagePublishLock.Unlock()
 			}
-			return nil
-		}
-
-		if imageUpload {
+		} else {
 			/* Processing image upload */
 			info, err = getImgPostInfo(d, r, builddir, post)
 			if err != nil {
 				return err
 			}
-		} else {
-			/* Processing image creation from container */
-			imagePublishLock.Lock()
-			info, err = imgPostContInfo(d, r, req, builddir)
-			if err != nil {
-				imagePublishLock.Unlock()
-				return err
-			}
-			imagePublishLock.Unlock()
-		}
-
-		metadata, err := imageBuildFromInfo(d, &info)
-		if err != nil {
-			return err
 		}
 
+		// Set the metadata
+		metadata := make(map[string]string)
+		metadata["fingerprint"] = info.Fingerprint
+		metadata["size"] = strconv.FormatInt(info.Size, 10)
 		op.UpdateMetadata(metadata)
 		return nil
 	}

From 240d86caea7a25e1c93516ee408a50335a85b23d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:49:07 -0500
Subject: [PATCH 2/4] images: Properly return the alias description
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>
---
 lxd/db_images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db_images.go b/lxd/db_images.go
index 5bd4f11..19f4269 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -227,7 +227,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 	aliases := []api.ImageAlias{}
 	for _, r := range results {
 		name = r[0].(string)
-		desc = r[0].(string)
+		desc = r[1].(string)
 		a := api.ImageAlias{Name: name, Description: desc}
 		aliases = append(aliases, a)
 	}

From 276d61a6df414c8b2f375117aa8868b24ee14634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:49:31 -0500
Subject: [PATCH 3/4] image: Show the alias description
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>
---
 lxc/image.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lxc/image.go b/lxc/image.go
index ba47e98..b996f32 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -335,7 +335,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		}
 		fmt.Println(i18n.G("Aliases:"))
 		for _, alias := range info.Aliases {
-			fmt.Printf("    - %s\n", alias.Name)
+			if alias.Description != "" {
+				fmt.Printf("    - %s (%s)\n", alias.Name, alias.Description)
+			} else {
+				fmt.Printf("    - %s\n", alias.Name)
+			}
 		}
 		fmt.Printf(i18n.G("Auto update: %s")+"\n", autoUpdate)
 		if info.UpdateSource != nil {

From e1bf66c098bc70532e5260603736adcedf070df6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:53:18 -0500
Subject: [PATCH 4/4] images: Allow setting aliases on creation/import
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2870

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md |  4 ++++
 doc/rest-api.md       | 12 ++++++++++++
 lxd/api_1.0.go        |  1 +
 lxd/images.go         | 18 ++++++++++++++++++
 shared/api/image.go   |  3 +++
 5 files changed, 38 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 22c7f4a..702fac9 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -225,3 +225,7 @@ When set, this will instruct LXD to attach to the specified VLAN. LXD
 will look for an existing interface for that VLAN on the host. If one
 can't be found it will create one itself and then use that as the
 macvlan parent.
+
+## image\_create\_aliases
+Adds a new "aliases" field to POST /1.0/images allowing for aliases to
+be set at image creation/import time.
diff --git a/doc/rest-api.md b/doc/rest-api.md
index 349b7b3..ba8a609 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -1253,6 +1253,10 @@ In the source image case, the following dict must be used:
         "properties": {                         # Image properties (optional, applied on top of source properties)
             "os": "Ubuntu"
         },
+        "aliases": [                            # Set initial aliases ("image_create_aliases" API extension)
+            {"name": "my-alias",
+             "description: "A description"
+        },
         "source": {
             "type": "image",
             "mode": "pull",                     # Only pull is supported for now
@@ -1274,6 +1278,10 @@ In the source container case, the following dict must be used:
         "properties": {                 # Image properties (optional)
             "os": "Ubuntu"
         },
+        "aliases": [                    # Set initial aliases ("image_create_aliases" API extension)
+            {"name": "my-alias",
+             "description: "A description"
+        },
         "source": {
             "type": "container",        # One of "container" or "snapshot"
             "name": "abc"
@@ -1288,6 +1296,10 @@ In the remote image URL case, the following dict must be used:
         "properties": {                                 # Image properties (optional)
             "os": "Ubuntu"
         },
+        "aliases": [                                    # Set initial aliases ("image_create_aliases" API extension)
+            {"name": "my-alias",
+             "description: "A description"
+        },
         "source": {
             "type": "url",
             "url": "https://www.some-server.com/image"  # URL for the image
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 9dfe7b5..a3fdd46 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -96,6 +96,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"storage_lvm_vg_rename",
 			"storage_lvm_thinpool_rename",
 			"network_vlan",
+			"image_create_aliases",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,
diff --git a/lxd/images.go b/lxd/images.go
index 06ac683..f869e96 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -742,6 +742,24 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 			}
 		}
 
+		// Apply any provided alias
+		for _, alias := range req.Aliases {
+			_, _, err := dbImageAliasGet(d.db, alias.Name, true)
+			if err == nil {
+				return fmt.Errorf("Alias already exists: %s", alias.Name)
+			}
+
+			id, _, err := dbImageGet(d.db, info.Fingerprint, false, false)
+			if err != nil {
+				return err
+			}
+
+			err = dbImageAliasAdd(d.db, alias.Name, id, alias.Description)
+			if err != nil {
+				return err
+			}
+		}
+
 		// Set the metadata
 		metadata := make(map[string]string)
 		metadata["fingerprint"] = info.Fingerprint
diff --git a/shared/api/image.go b/shared/api/image.go
index b162b82..789e887 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -13,6 +13,9 @@ type ImagesPost struct {
 
 	// API extension: image_compression_algorithm
 	CompressionAlgorithm string `json:"compression_algorithm" yaml:"compression_algorithm"`
+
+	// API extension: image_create_aliases
+	Aliases []ImageAlias `json:"aliases" yaml:"aliases"`
 }
 
 // ImagePut represents the modifiable fields of a LXD image


More information about the lxc-devel mailing list