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