[lxc-devel] [lxd/master] client: Add container template files operations

albertodonato on Github lxc-bot at linuxcontainers.org
Mon Jul 24 12:45:18 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 405 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170724/f5b71cda/attachment.bin>
-------------- next part --------------
From 9658d34230e135e7c2d646db378c247663657a79 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 24 Jul 2017 11:00:08 +0200
Subject: [PATCH] client: Add container template files operations.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 client/interfaces.go     |   6 ++
 client/lxd_containers.go | 100 +++++++++++++++++++++++++++++++
 doc/rest-api.md          |   2 +-
 lxc/config.go            | 152 +++++++++++++++++++++++++++++++++++++++++++++++
 test/suites/config.sh    |  16 +++--
 5 files changed, 269 insertions(+), 7 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index ed72b5c28..f0cee532e 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -89,6 +89,12 @@ type ContainerServer interface {
 	GetContainerMetadata(name string) (*api.ImageMetadata, string, error)
 	SetContainerMetadata(name string, metadata api.ImageMetadata, ETag string) error
 
+	GetContainerTemplateFiles(containerName string) (templates []string, err error)
+	GetContainerTemplateFile(containerName string, templateName string) (content io.ReadCloser, err error)
+	CreateContainerTemplateFile(containerName string, templateName string) (err error)
+	UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
+	DeleteContainerTemplateFile(name string, templateName string) (err error)
+
 	// Event handling functions
 	GetEvents() (listener *EventListener, err error)
 
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 00cf9d0e9..ff7ed61d0 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -1,6 +1,7 @@
 package lxd
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -1304,3 +1305,102 @@ func (r *ProtocolLXD) SetContainerMetadata(name string, metadata api.ImageMetada
 
 	return nil
 }
+
+// GetContainerTemplateFiles returns the list of name of a template files for a container.
+func (r *ProtocolLXD) GetContainerTemplateFiles(containerName string) ([]string, error) {
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	templates := []string{}
+
+	url := fmt.Sprintf("/containers/%s/metadata/templates", containerName)
+	_, err := r.queryStruct("GET", url, nil, "", &templates)
+	if err != nil {
+		return nil, err
+	}
+
+	return templates, nil
+}
+
+// GetContainerTemplateFile returns the content of a template file for a container.
+func (r *ProtocolLXD) GetContainerTemplateFile(containerName string, templateName string) (io.ReadCloser, error) {
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/1.0/containers/%s/metadata/templates?path=%s", r.httpHost, containerName, templateName)
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := r.parseResponse(resp)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return resp.Body, err
+}
+
+// CreateContainerTemplateFile creates an empty file template for a container.
+func (r *ProtocolLXD) CreateContainerTemplateFile(containerName string, templateName string) error {
+	return r.setContainerTemplateFile(containerName, templateName, bytes.NewReader(nil), "POST")
+}
+
+// UpdateContainerTemplateFile updates content for a container template file.
+func (r *ProtocolLXD) UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) error {
+	return r.setContainerTemplateFile(containerName, templateName, content, "PUT")
+}
+
+func (r *ProtocolLXD) setContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker, httpMethod string) error {
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/1.0/containers/%s/metadata/templates?path=%s", r.httpHost, containerName, templateName)
+	req, err := http.NewRequest(httpMethod, url, content)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("Content-Type", "application/octet-stream")
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := r.parseResponse(resp)
+		if err != nil {
+			return err
+		}
+	}
+	return err
+}
+
+// DeleteContainerTemplateFile deletes a template file for a container.
+func (r *ProtocolLXD) DeleteContainerTemplateFile(name string, templateName string) error {
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+	_, _, err := r.query("DELETE", fmt.Sprintf("/containers/%s/metadata/templates?path=%s", name, templateName), nil, "")
+	return err
+}
diff --git a/doc/rest-api.md b/doc/rest-api.md
index 6ba4cc8c7..cffafd85c 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -1268,7 +1268,7 @@ Return:
 * Operation: Sync
 * Return: the content of the template
 
-## POST (?path=\<template\>)
+### POST (?path=\<template\>)
 * Description: Add a continer template
 * Introduced: with API extension "container\_edit\_metadata"
 * Authentication: trusted
diff --git a/lxc/config.go b/lxc/config.go
index e66fb24ee..aaa58dff6 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"bytes"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
@@ -111,6 +112,24 @@ lxc config metadata show [<remote>:][container]
 lxc config metadata edit [<remote>:][container]
     Edit the container metadata.yaml, either by launching external editor or reading STDIN.
 
+*Container templates*
+
+lxc config template list [<remote>:][container]
+    List the names of template files for a container.
+
+lxc config template show [<remote>:][container] [template]
+    Show the content of a template file for a container.
+
+lxc config template create [<remote>:][container] [template]
+    Add an empty template file for a container.
+
+lxc config template edit [<remote>:][container] [template]
+    Edit the content of a template file for a container, either by launching external editor or reading STDIN.
+
+lxc config template delete [<remote>:][container] [template]
+    Delete a template file for a container.
+
+
 *Device management*
 
 lxc config device add [<remote>:]<container> <device> <type> [key=value...]
@@ -650,6 +669,73 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 			return errArgs
 		}
 
+	case "template":
+		if len(args) < 3 {
+			return errArgs
+		}
+
+		remote, container, err := conf.ParseRemote(args[2])
+		if err != nil {
+			return err
+		}
+
+		d, err := conf.GetContainerServer(remote)
+		if err != nil {
+			return err
+		}
+
+		switch args[1] {
+		case "list":
+			templates, err := d.GetContainerTemplateFiles(container)
+			if err != nil {
+				return err
+			}
+
+			c.listTemplateFiles(templates)
+			return nil
+
+		case "show":
+			if len(args) != 4 {
+				return errArgs
+			}
+			templateName := args[3]
+
+			template, err := d.GetContainerTemplateFile(container, templateName)
+			if err != nil {
+				return err
+			}
+			content, err := ioutil.ReadAll(template)
+			if err != nil {
+				return err
+			}
+			fmt.Printf("%s", content)
+			return nil
+
+		case "create":
+			if len(args) != 4 {
+				return errArgs
+			}
+			templateName := args[3]
+			return c.doContainerTemplateFileCreate(d, container, templateName)
+
+		case "edit":
+			if len(args) != 4 {
+				return errArgs
+			}
+			templateName := args[3]
+			return c.doContainerTemplateFileEdit(d, container, templateName)
+
+		case "delete":
+			if len(args) != 4 {
+				return errArgs
+			}
+			templateName := args[3]
+			return d.DeleteContainerTemplateFile(container, templateName)
+
+		default:
+			return errArgs
+		}
+
 	default:
 		return errArgs
 	}
@@ -657,6 +743,22 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 	return errArgs
 }
 
+func (c *configCmd) listTemplateFiles(templates []string) {
+	data := [][]string{}
+	for _, template := range templates {
+		data = append(data, []string{template})
+	}
+
+	table := tablewriter.NewWriter(os.Stdout)
+	table.SetAutoWrapText(false)
+	table.SetAlignment(tablewriter.ALIGN_LEFT)
+	table.SetRowLine(true)
+	table.SetHeader([]string{i18n.G("FILENAME")})
+	sort.Sort(byName(data))
+	table.AppendBulk(data)
+	table.Render()
+}
+
 func (c *configCmd) doContainerConfigEdit(client lxd.ContainerServer, cont string) error {
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
@@ -1246,3 +1348,53 @@ func (c *configCmd) doContainerMetadataEdit(client lxd.ContainerServer, name str
 
 	return nil
 }
+
+func (c *configCmd) doContainerTemplateFileCreate(client lxd.ContainerServer, containerName string, templateName string) error {
+	return client.CreateContainerTemplateFile(containerName, templateName)
+}
+
+func (c *configCmd) doContainerTemplateFileEdit(client lxd.ContainerServer, containerName string, templateName string) error {
+	if !termios.IsTerminal(int(syscall.Stdin)) {
+		return client.UpdateContainerTemplateFile(containerName, templateName, os.Stdin)
+	}
+
+	reader, err := client.GetContainerTemplateFile(containerName, templateName)
+	if err != nil {
+		return err
+	}
+	content, err := ioutil.ReadAll(reader)
+	if err != nil {
+		return err
+	}
+
+	// Spawn the editor
+	content, err = shared.TextEditor("", content)
+	if err != nil {
+		return err
+	}
+
+	for {
+		reader := bytes.NewReader(content)
+		err := client.UpdateContainerTemplateFile(containerName, templateName, reader)
+		// Respawn the editor
+		if err != nil {
+			fmt.Fprintf(os.Stderr, i18n.G("Error updating template file: %s")+"\n", err)
+			fmt.Println(i18n.G("Press enter to start the editor again"))
+
+			_, err := os.Stdin.Read(make([]byte, 1))
+			if err != nil {
+				return err
+			}
+
+			content, err = shared.TextEditor("", content)
+			if err != nil {
+				return err
+			}
+			continue
+		}
+
+		break
+	}
+
+	return nil
+}
diff --git a/test/suites/config.sh b/test/suites/config.sh
index 9b3bf551a..3231bd431 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -273,18 +273,22 @@ test_container_metadata() {
     lxc config metadata show c | grep -q BB
 
     # templates can be listed
-    curl -s --unix-socket "${LXD_DIR}/unix.socket" lxd/1.0/containers/c/metadata/templates | grep -q template.tpl
+    lxc config template list c | grep -q template.tpl
 
     # template content can be returned
-    curl -s --unix-socket "${LXD_DIR}/unix.socket" "lxd/1.0/containers/c/metadata/templates?path=template.tpl" | grep -q "name:"
+    lxc config template show c template.tpl | grep -q "name:"
 
     # templates can be added
-    curl -s --unix-socket "${LXD_DIR}/unix.socket" "lxd/1.0/containers/c/metadata/templates?path=my.tpl" -H 'Content-type: application/octet-stream' -d "some content"
-    curl -s --unix-socket "${LXD_DIR}/unix.socket" "lxd/1.0/containers/c/metadata/templates?path=my.tpl" | grep -q "some content"
+    lxc config template create c my.tpl
+    lxc config template list c | grep -q my.tpl
+
+    # template content can be updated
+    echo "some content" | lxc config template edit c my.tpl
+    lxc config template show c my.tpl | grep -q "some content"
     
     # templates can be removed
-    curl -s --unix-socket "${LXD_DIR}/unix.socket" "lxd/1.0/containers/c/metadata/templates?path=my.tpl" -X DELETE
-    ! (curl -s --unix-socket "${LXD_DIR}/unix.socket" lxd/1.0/containers/c/metadata/templates | grep -q my.tpl)
+    lxc config template delete c my.tpl
+    ! (lxc config template list c | grep -q my.tpl)
 
     lxc delete c
 }


More information about the lxc-devel mailing list