osbuild: add helper functions generating stages from fsnode types
Implement helper functions for generating osbuild stages for a slices of `fsnode` types, such as Files and Directories. The generated stages will ensure that the provided FS nodes will be created in the FS tree and will have their respective properties set (such as ownership, mode, etc). These functions are not yet used by any pipeline code, but the idea is that they will be used in pipeline generator functions to create custom directories and files based on the pipeline-specific customizations. Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
parent
0bd0ce9fc1
commit
fd79934843
2 changed files with 381 additions and 0 deletions
114
internal/osbuild/fsnode.go
Normal file
114
internal/osbuild/fsnode.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package osbuild
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/fsnode"
|
||||
)
|
||||
|
||||
// GenFileNodesStages generates the stages for a list of file nodes.
|
||||
// It generates the following stages:
|
||||
// - copy stage with all the files that need to be created by copying their
|
||||
// content from the list of inline sources. The SHA256 sum of the file is
|
||||
// used as the name of the stage input.
|
||||
// - chmod stage with all the files that need to have their permissions set.
|
||||
// - chown stage with all the files that need to have their ownership set.
|
||||
func GenFileNodesStages(files []*fsnode.File) []*Stage {
|
||||
var stages []*Stage
|
||||
var copyStagePaths []CopyStagePath
|
||||
copyStageInputs := make(CopyStageFilesInputs)
|
||||
chmodPaths := make(map[string]ChmodStagePathOptions)
|
||||
chownPaths := make(map[string]ChownStagePathOptions)
|
||||
|
||||
for _, file := range files {
|
||||
fileDataChecksum := fmt.Sprintf("%x", sha256.Sum256(file.Data()))
|
||||
copyStageInputKey := fmt.Sprintf("file-%s", fileDataChecksum)
|
||||
copyStagePaths = append(copyStagePaths, CopyStagePath{
|
||||
From: fmt.Sprintf("input://%s/sha256:%s", copyStageInputKey, fileDataChecksum),
|
||||
To: fmt.Sprintf("tree://%s", file.Path()),
|
||||
})
|
||||
copyStageInputs[copyStageInputKey] = NewFilesInput(NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
|
||||
NewFilesInputSourceArrayRefEntry(fileDataChecksum, nil),
|
||||
}))
|
||||
|
||||
if file.Mode() != nil {
|
||||
chmodPaths[file.Path()] = ChmodStagePathOptions{Mode: fmt.Sprintf("%#o", *file.Mode())}
|
||||
}
|
||||
|
||||
if file.User() != nil || file.Group() != nil {
|
||||
chownPaths[file.Path()] = ChownStagePathOptions{
|
||||
User: file.User(),
|
||||
Group: file.Group(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(copyStagePaths) > 0 {
|
||||
stages = append(stages, NewCopyStageSimple(&CopyStageOptions{Paths: copyStagePaths}, ©StageInputs))
|
||||
}
|
||||
|
||||
if len(chmodPaths) > 0 {
|
||||
stages = append(stages, NewChmodStage(&ChmodStageOptions{Items: chmodPaths}))
|
||||
}
|
||||
|
||||
if len(chownPaths) > 0 {
|
||||
stages = append(stages, NewChownStage(&ChownStageOptions{Items: chownPaths}))
|
||||
}
|
||||
|
||||
return stages
|
||||
}
|
||||
|
||||
// GenDirectoryNodesStages generates the stages for a list of directory nodes.
|
||||
// It generates the following stages:
|
||||
// - mkdir stage with all the directories that need to be created.
|
||||
// -- The existence of the directory will be gracefully handled only if no explicit permissions or ownership are
|
||||
// set.
|
||||
// - chmod stage with all the directories that need to have their permissions set.
|
||||
// - chown stage with all the directories that need to have their ownership set.
|
||||
func GenDirectoryNodesStages(dirs []*fsnode.Directory) []*Stage {
|
||||
var stages []*Stage
|
||||
var mkdirPaths []MkdirStagePath
|
||||
chmodPaths := make(map[string]ChmodStagePathOptions)
|
||||
chownPaths := make(map[string]ChownStagePathOptions)
|
||||
|
||||
for _, dir := range dirs {
|
||||
// Default to graceful handling of existing directories only if no explicit permissions or ownership are set.
|
||||
// This prevents the generated stages from changing the ownership and permissions of existing directories.
|
||||
// TODO: We may want to make this configurable if we end up internally using `fsnode.Directory` for other
|
||||
// purposes than BP customizations.
|
||||
dirExistOk := dir.Mode() == nil && dir.User() == nil && dir.Group() == nil
|
||||
|
||||
// Mode is intentionally not set, because it will be set by the chmod stage anyway.
|
||||
mkdirPaths = append(mkdirPaths, MkdirStagePath{
|
||||
Path: dir.Path(),
|
||||
Parents: dir.EnsureParentDirs(),
|
||||
ExistOk: dirExistOk,
|
||||
})
|
||||
|
||||
if dir.Mode() != nil {
|
||||
chmodPaths[dir.Path()] = ChmodStagePathOptions{Mode: fmt.Sprintf("%#o", *dir.Mode())}
|
||||
}
|
||||
|
||||
if dir.User() != nil || dir.Group() != nil {
|
||||
chownPaths[dir.Path()] = ChownStagePathOptions{
|
||||
User: dir.User(),
|
||||
Group: dir.Group(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(mkdirPaths) > 0 {
|
||||
stages = append(stages, NewMkdirStage(&MkdirStageOptions{Paths: mkdirPaths}))
|
||||
}
|
||||
|
||||
if len(chmodPaths) > 0 {
|
||||
stages = append(stages, NewChmodStage(&ChmodStageOptions{Items: chmodPaths}))
|
||||
}
|
||||
|
||||
if len(chownPaths) > 0 {
|
||||
stages = append(stages, NewChownStage(&ChownStageOptions{Items: chownPaths}))
|
||||
}
|
||||
|
||||
return stages
|
||||
}
|
||||
267
internal/osbuild/fsnode_test.go
Normal file
267
internal/osbuild/fsnode_test.go
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
package osbuild
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"github.com/osbuild/osbuild-composer/internal/fsnode"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenFileNodesStages(t *testing.T) {
|
||||
fileData1 := []byte("hello world")
|
||||
fileData2 := []byte("hello world 2")
|
||||
|
||||
ensureFileCreation := func(file *fsnode.File, err error) *fsnode.File {
|
||||
t.Helper()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, file)
|
||||
return file
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
files []*fsnode.File
|
||||
expected []*Stage
|
||||
}{
|
||||
{
|
||||
name: "empty-files-list",
|
||||
files: []*fsnode.File{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "nil-files-list",
|
||||
files: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "single-file-simple",
|
||||
files: []*fsnode.File{
|
||||
ensureFileCreation(fsnode.NewFile("/etc/file", nil, nil, nil, []byte(fileData1))),
|
||||
},
|
||||
expected: []*Stage{
|
||||
NewCopyStageSimple(&CopyStageOptions{
|
||||
Paths: []CopyStagePath{
|
||||
{
|
||||
From: fmt.Sprintf("input://file-%[1]x/sha256:%[1]x", sha256.Sum256(fileData1)),
|
||||
To: "tree:///etc/file",
|
||||
},
|
||||
},
|
||||
}, &CopyStageFilesInputs{
|
||||
fmt.Sprintf("file-%x", sha256.Sum256(fileData1)): NewFilesInput(NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
|
||||
NewFilesInputSourceArrayRefEntry(fmt.Sprintf("%x", sha256.Sum256(fileData1)), nil),
|
||||
})),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-files-simple",
|
||||
files: []*fsnode.File{
|
||||
ensureFileCreation(fsnode.NewFile("/etc/file", nil, nil, nil, []byte(fileData1))),
|
||||
ensureFileCreation(fsnode.NewFile("/etc/file2", nil, nil, nil, []byte(fileData2))),
|
||||
},
|
||||
expected: []*Stage{
|
||||
NewCopyStageSimple(&CopyStageOptions{
|
||||
Paths: []CopyStagePath{
|
||||
{
|
||||
From: fmt.Sprintf("input://file-%[1]x/sha256:%[1]x", sha256.Sum256(fileData1)),
|
||||
To: "tree:///etc/file",
|
||||
},
|
||||
{
|
||||
From: fmt.Sprintf("input://file-%[1]x/sha256:%[1]x", sha256.Sum256(fileData2)),
|
||||
To: "tree:///etc/file2",
|
||||
},
|
||||
},
|
||||
}, &CopyStageFilesInputs{
|
||||
fmt.Sprintf("file-%x", sha256.Sum256(fileData1)): NewFilesInput(NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
|
||||
NewFilesInputSourceArrayRefEntry(fmt.Sprintf("%x", sha256.Sum256(fileData1)), nil),
|
||||
})),
|
||||
fmt.Sprintf("file-%x", sha256.Sum256(fileData2)): NewFilesInput(NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
|
||||
NewFilesInputSourceArrayRefEntry(fmt.Sprintf("%x", sha256.Sum256(fileData2)), nil),
|
||||
})),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-files-with-all-options",
|
||||
files: []*fsnode.File{
|
||||
ensureFileCreation(fsnode.NewFile("/etc/file", common.ToPtr(os.FileMode(0644)), "root", int64(12345), []byte(fileData1))),
|
||||
ensureFileCreation(fsnode.NewFile("/etc/file2", common.ToPtr(os.FileMode(0755)), int64(12345), "root", []byte(fileData2))),
|
||||
},
|
||||
expected: []*Stage{
|
||||
NewCopyStageSimple(&CopyStageOptions{
|
||||
Paths: []CopyStagePath{
|
||||
{
|
||||
From: fmt.Sprintf("input://file-%[1]x/sha256:%[1]x", sha256.Sum256(fileData1)),
|
||||
To: "tree:///etc/file",
|
||||
},
|
||||
{
|
||||
From: fmt.Sprintf("input://file-%[1]x/sha256:%[1]x", sha256.Sum256(fileData2)),
|
||||
To: "tree:///etc/file2",
|
||||
},
|
||||
},
|
||||
}, &CopyStageFilesInputs{
|
||||
fmt.Sprintf("file-%x", sha256.Sum256(fileData1)): NewFilesInput(NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
|
||||
NewFilesInputSourceArrayRefEntry(fmt.Sprintf("%x", sha256.Sum256(fileData1)), nil),
|
||||
})),
|
||||
fmt.Sprintf("file-%x", sha256.Sum256(fileData2)): NewFilesInput(NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
|
||||
NewFilesInputSourceArrayRefEntry(fmt.Sprintf("%x", sha256.Sum256(fileData2)), nil),
|
||||
})),
|
||||
}),
|
||||
NewChmodStage(&ChmodStageOptions{
|
||||
Items: map[string]ChmodStagePathOptions{
|
||||
"/etc/file": {
|
||||
Mode: fmt.Sprintf("%#o", os.FileMode(0644)),
|
||||
},
|
||||
"/etc/file2": {
|
||||
Mode: fmt.Sprintf("%#o", os.FileMode(0755)),
|
||||
},
|
||||
},
|
||||
}),
|
||||
NewChownStage(&ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/file": {
|
||||
User: "root",
|
||||
Group: int64(12345),
|
||||
},
|
||||
"/etc/file2": {
|
||||
User: int64(12345),
|
||||
Group: "root",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotStages := GenFileNodesStages(tc.files)
|
||||
assert.EqualValues(t, tc.expected, gotStages)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenDirectoryNodesStages(t *testing.T) {
|
||||
|
||||
ensureDirCreation := func(dir *fsnode.Directory, err error) *fsnode.Directory {
|
||||
t.Helper()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, dir)
|
||||
return dir
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
dirs []*fsnode.Directory
|
||||
expected []*Stage
|
||||
}{
|
||||
{
|
||||
name: "empty-dirs-list",
|
||||
dirs: []*fsnode.Directory{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "nil-dirs-list",
|
||||
dirs: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "single-dir-simple",
|
||||
dirs: []*fsnode.Directory{
|
||||
ensureDirCreation(fsnode.NewDirectory("/etc/dir", nil, nil, nil, false)),
|
||||
},
|
||||
expected: []*Stage{
|
||||
NewMkdirStage(&MkdirStageOptions{
|
||||
Paths: []MkdirStagePath{
|
||||
{
|
||||
Path: "/etc/dir",
|
||||
ExistOk: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-dirs-simple",
|
||||
dirs: []*fsnode.Directory{
|
||||
ensureDirCreation(fsnode.NewDirectory("/etc/dir", nil, nil, nil, false)),
|
||||
ensureDirCreation(fsnode.NewDirectory("/etc/dir2", nil, nil, nil, false)),
|
||||
},
|
||||
expected: []*Stage{
|
||||
NewMkdirStage(&MkdirStageOptions{
|
||||
Paths: []MkdirStagePath{
|
||||
{
|
||||
Path: "/etc/dir",
|
||||
ExistOk: true,
|
||||
},
|
||||
{
|
||||
Path: "/etc/dir2",
|
||||
ExistOk: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-dirs-with-all-options",
|
||||
dirs: []*fsnode.Directory{
|
||||
ensureDirCreation(fsnode.NewDirectory("/etc/dir", common.ToPtr(os.FileMode(0700)), "root", int64(12345), true)),
|
||||
ensureDirCreation(fsnode.NewDirectory("/etc/dir2", common.ToPtr(os.FileMode(0755)), int64(12345), "root", false)),
|
||||
ensureDirCreation(fsnode.NewDirectory("/etc/dir3", nil, nil, nil, true)),
|
||||
},
|
||||
expected: []*Stage{
|
||||
NewMkdirStage(&MkdirStageOptions{
|
||||
Paths: []MkdirStagePath{
|
||||
{
|
||||
Path: "/etc/dir",
|
||||
Parents: true,
|
||||
ExistOk: false,
|
||||
},
|
||||
{
|
||||
Path: "/etc/dir2",
|
||||
ExistOk: false,
|
||||
},
|
||||
{
|
||||
Path: "/etc/dir3",
|
||||
Parents: true,
|
||||
ExistOk: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
NewChmodStage(&ChmodStageOptions{
|
||||
Items: map[string]ChmodStagePathOptions{
|
||||
"/etc/dir": {
|
||||
Mode: fmt.Sprintf("%#o", os.FileMode(0700)),
|
||||
},
|
||||
"/etc/dir2": {
|
||||
Mode: fmt.Sprintf("%#o", os.FileMode(0755)),
|
||||
},
|
||||
},
|
||||
}),
|
||||
NewChownStage(&ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/dir": {
|
||||
User: "root",
|
||||
Group: int64(12345),
|
||||
},
|
||||
"/etc/dir2": {
|
||||
User: int64(12345),
|
||||
Group: "root",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotStages := GenDirectoryNodesStages(tc.dirs)
|
||||
assert.EqualValues(t, tc.expected, gotStages)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue