[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