The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3041
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 6f3d99613ec5ff412c83eca6e7c022cb4ac9d2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel