[lxc-devel] [lxd/master] Implement template files for VMs

stgraber on Github lxc-bot at linuxcontainers.org
Mon Mar 2 14:33:36 UTC 2020


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/20200302/fe9c93ba/attachment.bin>
-------------- next part --------------
From db0da736dd3737f2d2071263857248edd02a4ac1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 Mar 2020 15:16:39 +0100
Subject: [PATCH 1/2] lxd/vm: Generate the template files
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/instance/drivers/driver_qemu.go | 146 ++++++++++++++++++++++++++++
 1 file changed, 146 insertions(+)

diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index a8f0e41006..4c9e633c70 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -17,10 +17,12 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/flosch/pongo2"
 	"github.com/gorilla/websocket"
 	"github.com/pborman/uuid"
 	"github.com/pkg/errors"
 	"golang.org/x/sys/unix"
+	"gopkg.in/yaml.v2"
 
 	lxdClient "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/backup"
@@ -41,6 +43,7 @@ import (
 	"github.com/lxc/lxd/lxd/state"
 	storagePools "github.com/lxc/lxd/lxd/storage"
 	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
+	pongoTemplate "github.com/lxc/lxd/lxd/template"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/lxd/vsock"
 	"github.com/lxc/lxd/shared"
@@ -1267,6 +1270,144 @@ echo "To start it now, unmount this filesystem and run: systemctl start lxd-agen
 		return err
 	}
 
+	// Templated files.
+	err = os.MkdirAll(filepath.Join(configDrivePath, "files"), 0500)
+	if err != nil {
+		return err
+	}
+
+	// Template anything that needs templating.
+	key := "volatile.apply_template"
+	if vm.localConfig[key] != "" {
+		// Run any template that needs running.
+		err = vm.templateApplyNow(vm.localConfig[key], filepath.Join(configDrivePath, "files"))
+		if err != nil {
+			return err
+		}
+
+		// Remove the volatile key from the DB.
+		err := vm.state.Cluster.ContainerConfigRemove(vm.id, key)
+		if err != nil {
+			return err
+		}
+	}
+
+	err = vm.templateApplyNow("start", filepath.Join(configDrivePath, "files"))
+	if err != nil {
+		return err
+	}
+
+	// Copy the template metadata itself too.
+	metaPath := filepath.Join(vm.Path(), "metadata.yaml")
+	if shared.PathExists(metaPath) {
+		err = shared.FileCopy(metaPath, filepath.Join(configDrivePath, "files/metadata.yaml"))
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (vm *qemu) templateApplyNow(trigger string, path string) error {
+	// If there's no metadata, just return.
+	fname := filepath.Join(vm.Path(), "metadata.yaml")
+	if !shared.PathExists(fname) {
+		return nil
+	}
+
+	// Parse the metadata.
+	content, err := ioutil.ReadFile(fname)
+	if err != nil {
+		return errors.Wrap(err, "Failed to read metadata")
+	}
+
+	metadata := new(api.ImageMetadata)
+	err = yaml.Unmarshal(content, &metadata)
+	if err != nil {
+		return errors.Wrapf(err, "Could not parse %s", fname)
+	}
+
+	// Figure out the instance architecture.
+	arch, err := osarch.ArchitectureName(vm.architecture)
+	if err != nil {
+		arch, err = osarch.ArchitectureName(vm.state.OS.Architectures[0])
+		if err != nil {
+			return errors.Wrap(err, "Failed to detect system architecture")
+		}
+	}
+
+	// Generate the container metadata
+	instanceMeta := make(map[string]string)
+	instanceMeta["name"] = vm.name
+	instanceMeta["architecture"] = arch
+
+	if vm.ephemeral {
+		instanceMeta["ephemeral"] = "true"
+	} else {
+		instanceMeta["ephemeral"] = "false"
+	}
+
+	// Go through the templates
+	for tplPath, tpl := range metadata.Templates {
+		var w *os.File
+
+		// Check if the template should be applied now
+		found := false
+		for _, tplTrigger := range tpl.When {
+			if tplTrigger == trigger {
+				found = true
+				break
+			}
+		}
+
+		if !found {
+			continue
+		}
+
+		// Create the file itself
+		w, err = os.Create(filepath.Join(path, fmt.Sprintf("%s.out", tpl.Template)))
+		if err != nil {
+			return err
+		}
+
+		// Fix ownership and mode
+		w.Chmod(0644)
+		defer w.Close()
+
+		// Read the template
+		tplString, err := ioutil.ReadFile(filepath.Join(vm.TemplatesPath(), tpl.Template))
+		if err != nil {
+			return errors.Wrap(err, "Failed to read template file")
+		}
+
+		// Restrict filesystem access to within the container's rootfs
+		tplSet := pongo2.NewSet(fmt.Sprintf("%s-%s", vm.name, tpl.Template), pongoTemplate.ChrootLoader{Path: vm.TemplatesPath()})
+		tplRender, err := tplSet.FromString("{% autoescape off %}" + string(tplString) + "{% endautoescape %}")
+		if err != nil {
+			return errors.Wrap(err, "Failed to render template")
+		}
+
+		configGet := func(confKey, confDefault *pongo2.Value) *pongo2.Value {
+			val, ok := vm.expandedConfig[confKey.String()]
+			if !ok {
+				return confDefault
+			}
+
+			return pongo2.AsValue(strings.TrimRight(val, "\r\n"))
+		}
+
+		// Render the template
+		tplRender.ExecuteWriter(pongo2.Context{"trigger": trigger,
+			"path":       tplPath,
+			"instance":   instanceMeta,
+			"container":  instanceMeta, // FIXME: remove once most images have moved away.
+			"config":     vm.expandedConfig,
+			"devices":    vm.expandedDevices,
+			"properties": tpl.Properties,
+			"config_get": configGet}, w)
+	}
+
 	return nil
 }
 
@@ -3436,6 +3577,11 @@ func (vm *qemu) StorageStop() (bool, error) {
 
 // DeferTemplateApply not used currently.
 func (vm *qemu) DeferTemplateApply(trigger string) error {
+	err := vm.VolatileSet(map[string]string{"volatile.apply_template": trigger})
+	if err != nil {
+		return errors.Wrap(err, "Failed to set apply_template volatile key")
+	}
+
 	return nil
 }
 

From 381cedceb7f601fb17404e75a3902018bdd90cf2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 2 Mar 2020 15:22:02 +0100
Subject: [PATCH 2/2] lxd-agent: Put templates in place
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-agent/main_agent.go | 38 ++++++++++++------
 lxd-agent/templates.go  | 86 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 112 insertions(+), 12 deletions(-)
 create mode 100644 lxd-agent/templates.go

diff --git a/lxd-agent/main_agent.go b/lxd-agent/main_agent.go
index 3a6aaf9fa9..8b05ae7c3c 100644
--- a/lxd-agent/main_agent.go
+++ b/lxd-agent/main_agent.go
@@ -3,9 +3,9 @@ package main
 import (
 	"context"
 	"fmt"
+	"io"
 	"os"
 	"os/signal"
-	"path/filepath"
 	"time"
 
 	"github.com/pkg/errors"
@@ -49,24 +49,38 @@ func (c *cmdAgent) Run(cmd *cobra.Command, args []string) error {
 	logger.Info("lxd-agent starting")
 	defer logger.Info("lxd-agent stopped")
 
-	// Setup cloud-init.
-	if shared.PathExists("/etc/cloud") && !shared.PathExists("/var/lib/cloud/seed/nocloud-net") {
-		err := os.MkdirAll("/var/lib/cloud/seed/nocloud-net/", 0700)
+	// Apply the templated files.
+	files, err := templatesApply("files/")
+	if err != nil {
+		return err
+	}
+
+	// Sync the hostname.
+	if shared.PathExists("/proc/sys/kernel/hostname") && shared.StringInSlice("/etc/hostname", files) {
+		// Open the two files.
+		src, err := os.Open("/etc/hostname")
 		if err != nil {
 			return err
 		}
 
-		for _, fName := range []string{"meta-data", "user-data", "vendor-data", "network-config"} {
-			if !shared.PathExists(filepath.Join("cloud-init", fName)) {
-				continue
-			}
+		dst, err := os.Create("/proc/sys/kernel/hostname")
+		if err != nil {
+			return err
+		}
 
-			err := shared.FileCopy(filepath.Join("cloud-init", fName), filepath.Join("/var/lib/cloud/seed/nocloud-net", fName))
-			if err != nil {
-				return err
-			}
+		// Copy the data.
+		_, err = io.Copy(dst, src)
+		if err != nil {
+			return err
 		}
 
+		// Close the files.
+		src.Close()
+		dst.Close()
+	}
+
+	// Run cloud-init.
+	if shared.PathExists("/etc/cloud") && shared.StringInSlice("/var/lib/cloud/seed/nocloud-net/meta-data", files) {
 		if shared.PathExists("/run/cloud-init") {
 			err = os.RemoveAll("/run/cloud-init")
 			if err != nil {
diff --git a/lxd-agent/templates.go b/lxd-agent/templates.go
new file mode 100644
index 0000000000..8a2833d892
--- /dev/null
+++ b/lxd-agent/templates.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v2"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+func templatesApply(path string) ([]string, error) {
+	metaName := filepath.Join(path, "metadata.yaml")
+	if !shared.PathExists(metaName) {
+		return nil, nil
+	}
+
+	// Parse the metadata.
+	content, err := ioutil.ReadFile(metaName)
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to read metadata")
+	}
+
+	metadata := new(api.ImageMetadata)
+	err = yaml.Unmarshal(content, &metadata)
+	if err != nil {
+		return nil, errors.Wrap(err, "Could not parse metadata.yaml")
+	}
+
+	// Go through the files and copy them into place.
+	files := []string{}
+	for tplPath, tpl := range metadata.Templates {
+		filePath := filepath.Join(path, fmt.Sprintf("%s.out", tpl.Template))
+
+		if !shared.PathExists(filePath) {
+			continue
+		}
+
+		var w *os.File
+		if shared.PathExists(tplPath) {
+			if tpl.CreateOnly {
+				continue
+			}
+
+			// Open the existing file.
+			w, err = os.Create(tplPath)
+			if err != nil {
+				return nil, errors.Wrap(err, "Failed to create template file")
+			}
+		} else {
+			// Create the directories leading to the file.
+			os.MkdirAll(filepath.Dir(tplPath), 0755)
+
+			// Create the file itself.
+			w, err = os.Create(tplPath)
+			if err != nil {
+				return nil, err
+			}
+
+			// Fix mode.
+			w.Chmod(0644)
+		}
+		defer w.Close()
+
+		// Do the copy.
+		src, err := os.Open(filePath)
+		if err != nil {
+			return nil, err
+		}
+		defer src.Close()
+
+		_, err = io.Copy(w, src)
+		if err != nil {
+			return nil, err
+		}
+
+		files = append(files, tplPath)
+	}
+
+	return files, nil
+}


More information about the lxc-devel mailing list