The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3584
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) === Add a new `lxc config template <op>` section to the CLI to perform operations related to template files.
From 9658d34230e135e7c2d646db378c247663657a79 Mon Sep 17 00:00:00 2001 From: Alberto Donato <[email protected]> Date: Mon, 24 Jul 2017 11:00:08 +0200 Subject: [PATCH] client: Add container template files operations. Signed-off-by: Alberto Donato <[email protected]> --- 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 }
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
