elemental-toolkit/pkg/cloudinit/cloudinit_test.go

354 lines
9.7 KiB
Go

/*
Copyright © 2022 - 2025 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudinit_test
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"github.com/jaypipes/ghw/pkg/block"
"github.com/rancher/yip/pkg/schema"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
. "github.com/rancher/elemental-toolkit/v2/pkg/cloudinit"
"github.com/rancher/elemental-toolkit/v2/pkg/constants"
"github.com/rancher/elemental-toolkit/v2/pkg/mocks"
"github.com/rancher/elemental-toolkit/v2/pkg/types"
"github.com/rancher/elemental-toolkit/v2/pkg/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
// Parted print sample output
const printOutput = `BYT;
/dev/loop0:50593792s:loopback:512:512:msdos:Loopback device:;
1:2048s:98303s:96256s:ext4::type=83;
2:98304s:29394943s:29296640s:ext4::boot, type=83;
3:29394944s:45019135s:15624192s:ext4::type=83;`
var _ = Describe("CloudRunner", Label("CloudRunner", "types", "cloud-init"), func() {
// unit test stolen from yip
Describe("loading yaml files", func() {
var logger types.Logger
var buffer *bytes.Buffer
BeforeEach(func() {
buffer = bytes.NewBuffer([]byte{})
logger = types.NewBufferLogger(buffer)
logger.SetLevel(types.DebugLevel())
})
It("executes commands", func() {
fs2, cleanup2, err := vfst.NewTestFS(map[string]interface{}{})
Expect(err).Should(BeNil())
temp := fs2.TempDir()
defer cleanup2()
fs, cleanup, err := vfst.NewTestFS(map[string]interface{}{
"/some/yip/01_first.yaml": `
stages:
test:
- commands:
- sed -i 's/boo/bar/g' ` + temp + `/tmp/test/bar
`,
"/some/yip/02_second.yaml": `
stages:
test:
- commands:
- sed -i 's/bar/baz/g' ` + temp + `/tmp/test/bar
`,
})
Expect(err).Should(BeNil())
defer cleanup()
err = fs2.Mkdir("/tmp", os.ModePerm)
Expect(err).Should(BeNil())
err = fs2.Mkdir("/tmp/test", os.ModePerm)
Expect(err).Should(BeNil())
err = fs2.WriteFile("/tmp/test/bar", []byte(`boo`), os.ModePerm)
Expect(err).Should(BeNil())
runner := NewYipCloudInitRunner(logger, &types.RealRunner{}, fs)
err = runner.Run("test", "/some/yip")
Expect(err).Should(BeNil())
file, err := os.Open(temp + "/tmp/test/bar")
Expect(err).ShouldNot(HaveOccurred())
b, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
Expect(string(b)).Should(Equal("baz"))
})
})
Describe("writing yaml files", func() {
var fs *vfst.TestFS
var logger types.Logger
var cleanup func()
var err error
var yipRunner *YipCloudInitRunner
var tempDir string
BeforeEach(func() {
logger = types.NewNullLogger()
fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{})
Expect(err).Should(BeNil())
yipRunner = NewYipCloudInitRunner(logger, &types.RealRunner{}, fs)
tempDir = fs.TempDir()
})
AfterEach(func() {
cleanup()
})
It("produces yaml files and is capable to executed them", func() {
conf := &schema.YipConfig{
Name: "Example cloud-config file",
Stages: map[string][]schema.Stage{
"hello-test": {
schema.Stage{
Name: "Some step",
Commands: []string{
"echo 'Hello world' > " + tempDir + "/output/hello",
},
},
},
},
}
Expect(utils.MkdirAll(fs, "/output", constants.DirPerm)).To(Succeed())
Expect(yipRunner.CloudInitFileRender("/conf/exmaple.yaml", conf)).To(Succeed())
Expect(yipRunner.Run("hello-test", "/conf")).To(Succeed())
data, err := fs.ReadFile("/output/hello")
Expect(err).To(BeNil())
Expect(string(data)).To(Equal("Hello world\n"))
})
It("fails writing a file on read only filesystem", func() {
conf := &schema.YipConfig{
Name: "Example cloud-config file",
Stages: map[string][]schema.Stage{
"hello-test": {
schema.Stage{
Name: "Some step",
Commands: []string{
"echo 'Hello world' > " + tempDir + "/output/hello",
},
},
},
},
}
Expect(utils.MkdirAll(fs, "/output", constants.DirPerm)).To(Succeed())
roFS := vfs.NewReadOnlyFS(fs)
yipRunner = NewYipCloudInitRunner(logger, &types.RealRunner{}, roFS)
Expect(yipRunner.CloudInitFileRender("/conf/exmaple.yaml", conf)).NotTo(Succeed())
})
})
Describe("layout plugin execution", func() {
var runner *mocks.FakeRunner
var afs *vfst.TestFS
var device, cmdFail string
var partNum int
var cleanup func()
logger := types.NewNullLogger()
BeforeEach(func() {
afs, cleanup, _ = vfst.NewTestFS(nil)
err := utils.MkdirAll(afs, "/some/yip", constants.DirPerm)
Expect(err).To(BeNil())
_ = utils.MkdirAll(afs, "/dev", constants.DirPerm)
device = "/dev/device"
_, err = afs.Create(device)
Expect(err).To(BeNil())
runner = mocks.NewFakeRunner()
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
if cmd == cmdFail {
return []byte{}, errors.New("command error")
}
switch cmd {
case "parted":
return []byte(printOutput), nil
default:
return []byte{}, nil
}
}
})
AfterEach(func() {
cleanup()
})
It("Does nothing if no changes are defined", func() {
err := afs.WriteFile("/some/yip/layout.yaml", []byte(fmt.Sprintf(`
stages:
test:
- name: Nothing to do
layout:
- name: Empty device, does nothing
layout:
device:
label: ""
path: ""
- name: Defined device without partitions, does nothing
layout:
device:
path: %s
- name: Defined already existing partition, does nothing
layout:
device:
label: DEV_LABEL
add_partitions:
- fsLabel: DEV_LABEL
pLabel: partLabel
`, device)), constants.FilePerm)
Expect(err).To(BeNil())
ghwTest := mocks.GhwMock{}
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
{
Name: "device1",
FilesystemLabel: "DEV_LABEL",
},
}}
ghwTest.AddDisk(disk)
ghwTest.CreateDevices()
defer ghwTest.Clean()
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).To(BeNil())
})
It("Expands last partition on a MSDOS disk", func() {
partNum = 3
_, err := afs.Create(fmt.Sprintf("%s%d", device, partNum))
Expect(err).To(BeNil())
err = afs.WriteFile("/some/yip/layout.yaml", []byte(fmt.Sprintf(`
stages:
test:
- name: Expanding last partition
layout:
device:
path: %s
expand_partition:
size: 0
`, device)), constants.FilePerm)
Expect(err).To(BeNil())
ghwTest := mocks.GhwMock{}
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
{
Name: fmt.Sprintf("device%d", partNum),
Type: "ext4",
},
}}
ghwTest.AddDisk(disk)
ghwTest.CreateDevices()
defer ghwTest.Clean()
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).To(BeNil())
})
It("Adds a partition on a MSDOS disk", func() {
partNum = 4
_, err := afs.Create(fmt.Sprintf("%s%d", device, partNum))
Expect(err).To(BeNil())
err = afs.WriteFile("/some/yip/layout.yaml", []byte(fmt.Sprintf(`
stages:
test:
- name: Adding new partition
layout:
device:
path: %s
add_partitions:
- fsLabel: SOMELABEL
pLabel: somelabel
`, device)), constants.FilePerm)
Expect(err).To(BeNil())
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).To(BeNil())
})
It("Fails to add a partition on a MSDOS disk", func() {
cmdFail = "mkfs.ext4"
partNum = 4
_, err := afs.Create(fmt.Sprintf("%s%d", device, partNum))
Expect(err).To(BeNil())
err = afs.WriteFile("/some/yip/layout.yaml", []byte(fmt.Sprintf(`
stages:
test:
- name: Adding new partition
layout:
device:
path: %s
add_partitions:
- fsLabel: SOMELABEL
pLabel: somelabel
`, device)), constants.FilePerm)
Expect(err).To(BeNil())
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).NotTo(BeNil())
})
It("Fails to expand last partition", func() {
partNum = 3
cmdFail = "resize2fs"
_, err := afs.Create(fmt.Sprintf("%s%d", device, partNum))
Expect(err).To(BeNil())
err = afs.WriteFile("/some/yip/layout.yaml", []byte(fmt.Sprintf(`
stages:
test:
- name: Expanding last partition
layout:
device:
path: %s
expand_partition:
size: 0
`, device)), constants.FilePerm)
Expect(err).To(BeNil())
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).NotTo(BeNil())
})
It("Fails to find device by path", func() {
err := afs.WriteFile("/some/yip/layout.yaml", []byte(`
stages:
test:
- name: Missing device path
layout:
device:
path: /whatever
`), constants.FilePerm)
Expect(err).To(BeNil())
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).NotTo(BeNil())
})
It("Fails to find device by label", func() {
err := afs.WriteFile("/some/yip/layout.yaml", []byte(`
stages:
test:
- name: Missing device label
layout:
device:
label: IM_NOT_THERE
`), constants.FilePerm)
Expect(err).To(BeNil())
cloudRunner := NewYipCloudInitRunner(logger, runner, afs)
Expect(cloudRunner.Run("test", "/some/yip")).NotTo(BeNil())
})
})
})