[lxc-devel] [distrobuilder/master] generators: Fix cloud-init

monstermunchkin on Github lxc-bot at linuxcontainers.org
Wed Aug 14 16:14:08 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 518 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190814/46f65a81/attachment.bin>
-------------- next part --------------
From 859a1030e080298608707297e251945240aac69a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 14 Aug 2019 15:42:00 +0200
Subject: [PATCH] generators: Fix cloud-init

Make sure the cloud-init generator doesn't leave any files behind when
building an lxc image.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 generators/cloud-init.go      | 27 ++++++++---
 generators/cloud-init_test.go | 85 ++++++++++++++++++++++++++++++++++-
 generators/generators.go      | 62 ++++++++++++++++++++-----
 3 files changed, 155 insertions(+), 19 deletions(-)

diff --git a/generators/cloud-init.go b/generators/cloud-init.go
index ed89e36..1e16780 100644
--- a/generators/cloud-init.go
+++ b/generators/cloud-init.go
@@ -22,16 +22,16 @@ func (g CloudInitGenerator) RunLXC(cacheDir, sourceDir string, img *image.LXCIma
 	defFile shared.DefinitionFile) error {
 	// With OpenRC:
 	// Remove all symlinks to /etc/init.d/cloud-{init-local,config,init,final} in /etc/runlevels/*
-	fullPath := filepath.Join(sourceDir, "/etc/runlevels")
+	fullPath := filepath.Join(sourceDir, "etc", "runlevels")
 
 	if lxd.PathExists(fullPath) {
-		filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
+		err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
 			if info.IsDir() {
 				return nil
 			}
 
 			if lxd.StringInSlice(info.Name(), []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"}) {
-				err := os.Remove(path)
+				err := StoreFile(cacheDir, sourceDir, strings.TrimPrefix(path, sourceDir))
 				if err != nil {
 					return err
 				}
@@ -39,6 +39,9 @@ func (g CloudInitGenerator) RunLXC(cacheDir, sourceDir string, img *image.LXCIma
 
 			return nil
 		})
+		if err != nil {
+			return err
+		}
 	}
 
 	// With upstart:
@@ -58,7 +61,7 @@ func (g CloudInitGenerator) RunLXC(cacheDir, sourceDir string, img *image.LXCIma
 			}
 
 			if re.MatchString(info.Name()) {
-				err := os.Remove(path)
+				err := StoreFile(cacheDir, sourceDir, strings.TrimPrefix(path, sourceDir))
 				if err != nil {
 					return err
 				}
@@ -69,12 +72,24 @@ func (g CloudInitGenerator) RunLXC(cacheDir, sourceDir string, img *image.LXCIma
 	}
 
 	// With systemd:
-	// Create file /etc/cloud/cloud-init.disabled
-	err := os.MkdirAll(filepath.Join(sourceDir, "/etc/cloud"), 0755)
+	if !lxd.PathExists(filepath.Join(sourceDir, "/etc/cloud")) {
+		err := StoreFile(cacheDir, sourceDir, "/etc/cloud")
+		if err != nil {
+			return err
+		}
+
+		err = os.MkdirAll(filepath.Join(sourceDir, "/etc/cloud"), 0755)
+		if err != nil {
+			return err
+		}
+	}
+
+	err := StoreFile(cacheDir, sourceDir, "/etc/cloud/cloud-init.disabled")
 	if err != nil {
 		return err
 	}
 
+	// Create file /etc/cloud/cloud-init.disabled
 	f, err := os.Create(filepath.Join(sourceDir, "/etc/cloud/cloud-init.disabled"))
 	if err != nil {
 		return err
diff --git a/generators/cloud-init_test.go b/generators/cloud-init_test.go
index c6b341f..e07916d 100644
--- a/generators/cloud-init_test.go
+++ b/generators/cloud-init_test.go
@@ -7,13 +7,94 @@ import (
 	"path/filepath"
 	"testing"
 
+	lxd "github.com/lxc/lxd/shared"
+	"github.com/stretchr/testify/require"
+
 	"github.com/lxc/distrobuilder/image"
 	"github.com/lxc/distrobuilder/shared"
-	"github.com/stretchr/testify/require"
 )
 
-func TestCloudInitGeneratorRunLXD(t *testing.T) {
+func TestCloudInitGeneratorRunLXC(t *testing.T) {
+	cacheDir := filepath.Join(os.TempDir(), "distrobuilder-test")
+	rootfsDir := filepath.Join(cacheDir, "rootfs")
+
+	setup(t, cacheDir)
+	defer teardown(cacheDir)
+
+	generator := Get("cloud-init")
+	require.Equal(t, CloudInitGenerator{}, generator)
+
+	// Prepare rootfs
+	err := os.MkdirAll(filepath.Join(rootfsDir, "etc", "runlevels"), 0755)
+	require.NoError(t, err)
+
+	err = os.MkdirAll(filepath.Join(rootfsDir, "etc", "cloud"), 0755)
+	require.NoError(t, err)
+
+	for _, f := range []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"} {
+		fullPath := filepath.Join(rootfsDir, "etc", "runlevels", f)
+		err = os.Symlink("/dev/null", fullPath)
+		require.NoError(t, err)
+		require.FileExists(t, fullPath)
+	}
+
+	for i := 0; i <= 6; i++ {
+		dir := filepath.Join(rootfsDir, "etc", "rc.d", fmt.Sprintf("rc%d.d", i))
+
+		err = os.MkdirAll(dir, 0755)
+		require.NoError(t, err)
+
+		for _, f := range []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"} {
+			fullPath := filepath.Join(dir, fmt.Sprintf("S99%s", f))
+			err = os.Symlink("/dev/null", fullPath)
+			require.NoError(t, err)
+			require.FileExists(t, fullPath)
+		}
+	}
+
+	// Disable cloud-init
+	generator.RunLXC(cacheDir, rootfsDir, nil, shared.DefinitionFile{})
+
+	// Check whether the generator has altered the rootfs
+	for _, f := range []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"} {
+		fullPath := filepath.Join(rootfsDir, "etc", "runlevels", f)
+		require.Falsef(t, lxd.PathExists(fullPath), "File '%s' exists but shouldn't", fullPath)
+	}
+
+	for i := 0; i <= 6; i++ {
+		dir := filepath.Join(rootfsDir, "etc", "rc.d", fmt.Sprintf("rc%d.d", i))
+
+		for _, f := range []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"} {
+			fullPath := filepath.Join(dir, fmt.Sprintf("S99%s", f))
+			require.Falsef(t, lxd.PathExists(fullPath), "File '%s' exists but shouldn't", fullPath)
+		}
+	}
+
+	require.FileExists(t, filepath.Join(rootfsDir, "etc", "cloud", "cloud-init.disabled"))
 
+	err = RestoreFiles(cacheDir, rootfsDir)
+	require.NoError(t, err)
+
+	// Check whether the files have been restored
+	for _, f := range []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"} {
+		fullPath := filepath.Join(rootfsDir, "etc", "runlevels", f)
+		require.FileExists(t, fullPath)
+	}
+
+	for i := 0; i <= 6; i++ {
+		dir := filepath.Join(rootfsDir, "etc", "rc.d", fmt.Sprintf("rc%d.d", i))
+
+		for _, f := range []string{"cloud-init-local", "cloud-config", "cloud-init", "cloud-final"} {
+			fullPath := filepath.Join(dir, fmt.Sprintf("S99%s", f))
+			require.FileExists(t, fullPath)
+		}
+	}
+
+	fullPath := filepath.Join(rootfsDir, "etc", "cloud", "cloud-init.disabled")
+	require.Falsef(t, lxd.PathExists(fullPath), "File '%s' exists but shouldn't", fullPath)
+}
+
+func TestCloudInitGeneratorRunLXD(t *testing.T) {
 	cacheDir := filepath.Join(os.TempDir(), "distrobuilder-test")
 	rootfsDir := filepath.Join(cacheDir, "rootfs")
 
diff --git a/generators/generators.go b/generators/generators.go
index f4f1ef6..3842852 100644
--- a/generators/generators.go
+++ b/generators/generators.go
@@ -4,6 +4,7 @@ import (
 	"os"
 	p "path"
 	"path/filepath"
+	"strings"
 
 	lxd "github.com/lxc/lxd/shared"
 
@@ -40,13 +41,21 @@ func Get(generator string) Generator {
 	return nil
 }
 
-var storedFiles = map[string]string{}
+var storedFiles = map[string]os.FileInfo{}
 
 // StoreFile caches a file which can be restored with the RestoreFiles function.
 func StoreFile(cacheDir, sourceDir, path string) error {
+	fullPath := filepath.Join(sourceDir, path)
+
+	_, ok := storedFiles[fullPath]
+	if ok {
+		// This file or directory has already been recorded
+		return nil
+	}
+
 	// Record newly created files
-	if !lxd.PathExists(filepath.Join(sourceDir, path)) {
-		storedFiles[filepath.Join(sourceDir, path)] = ""
+	if !lxd.PathExists(fullPath) {
+		storedFiles[fullPath] = nil
 		return nil
 	}
 
@@ -56,18 +65,39 @@ func StoreFile(cacheDir, sourceDir, path string) error {
 		return err
 	}
 
-	storedFiles[filepath.Join(sourceDir, path)] = filepath.Join(cacheDir, "tmp", path)
+	info, err := os.Lstat(fullPath)
+	if err != nil {
+		return err
+	}
+
+	storedFiles[fullPath] = info
+
+	err = os.Rename(fullPath, filepath.Join(cacheDir, "tmp", path))
+	if err == nil {
+		return nil
+	}
 
-	return lxd.FileCopy(filepath.Join(sourceDir, path),
-		filepath.Join(cacheDir, "tmp", path))
+	// Try copying the file since renaming it failed
+	if info.IsDir() {
+		err = lxd.DirCopy(fullPath, filepath.Join(cacheDir, "tmp", path))
+	} else {
+		err = lxd.FileCopy(fullPath, filepath.Join(cacheDir, "tmp", path))
+	}
+	if err != nil {
+		return err
+	}
+
+	return os.RemoveAll(fullPath)
 }
 
 // RestoreFiles restores original files which were cached by StoreFile.
 func RestoreFiles(cacheDir, sourceDir string) error {
-	for origPath, tmpPath := range storedFiles {
+	var err error
+
+	for origPath, fi := range storedFiles {
 		// Deal with newly created files
-		if tmpPath == "" {
-			err := os.Remove(origPath)
+		if fi == nil {
+			err := os.RemoveAll(origPath)
 			if err != nil {
 				return err
 			}
@@ -75,14 +105,24 @@ func RestoreFiles(cacheDir, sourceDir string) error {
 			continue
 		}
 
-		err := lxd.FileCopy(tmpPath, origPath)
+		err = os.Rename(filepath.Join(cacheDir, "tmp", strings.TrimPrefix(origPath, sourceDir)), origPath)
+		if err == nil {
+			continue
+		}
+
+		// Try copying the file or directory since renaming it failed
+		if fi.IsDir() {
+			err = lxd.DirCopy(filepath.Join(cacheDir, "tmp", strings.TrimPrefix(origPath, sourceDir)), origPath)
+		} else {
+			err = lxd.FileCopy(filepath.Join(cacheDir, "tmp", strings.TrimPrefix(origPath, sourceDir)), origPath)
+		}
 		if err != nil {
 			return err
 		}
 	}
 
 	// Reset the list of stored files
-	storedFiles = map[string]string{}
+	storedFiles = map[string]os.FileInfo{}
 
 	return nil
 }


More information about the lxc-devel mailing list