Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

base/v0_6_exp: add parent directory sugar #508

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions base/v0_6_exp/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ type File struct {
Append []Resource `yaml:"append"`
Contents Resource `yaml:"contents"`
Mode *int `yaml:"mode"`
Parent Parent `yaml:"parent"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also need to add this to Directory and Link.

}

type Parent struct {
Path *string `yaml:"path,omitempty"`
Mode *int `yaml:"mode,omitempty"`
}

type Filesystem struct {
Expand Down
82 changes: 77 additions & 5 deletions base/v0_6_exp/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf

tr := translate.NewTranslator("yaml", "json", options)
tr.AddCustomTranslator(translateIgnition)
tr.AddCustomTranslator(translateFile)
tr.AddCustomTranslator(translateDirectory)
tr.AddCustomTranslator(translateLink)
tr.AddCustomTranslator(translateStorage)
tr.AddCustomTranslator(translateResource)
tr.AddCustomTranslator(translatePasswdUser)
tr.AddCustomTranslator(translateUnit)
Expand All @@ -99,7 +97,6 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf
translate.MergeP(tr, tm, &r, "systemd", &c.Systemd, &ret.Systemd)

c.addMountUnits(&ret, &tm)

tm2, r2 := c.processTrees(&ret, options)
tm.Merge(tm2)
r.Merge(r2)
Expand All @@ -121,6 +118,65 @@ func translateIgnition(from Ignition, options common.TranslateOptions) (to types
return
}

func translateStorage(from Storage, options common.TranslateOptions) (to types.Storage, tm translate.TranslationSet, r report.Report) {
tr := translate.NewTranslator("yaml", "json", options)
tr.AddCustomTranslator(translateFile)
tr.AddCustomTranslator(translateDirectory)
tr.AddCustomTranslator(translateLink)
tr.AddCustomTranslator(translateLuks)
tm, r = translate.Prefixed(tr, "directories", &from.Directories, &to.Directories)
translate.MergeP(tr, tm, &r, "disks", &from.Disks, &to.Disks)
translate.MergeP(tr, tm, &r, "files", &from.Files, &to.Files)
translate.MergeP(tr, tm, &r, "filesystems", &from.Filesystems, &to.Filesystems)
translate.MergeP(tr, tm, &r, "links", &from.Links, &to.Links)
translate.MergeP(tr, tm, &r, "luks", &from.Luks, &to.Luks)
translate.MergeP(tr, tm, &r, "raid", &from.Raid, &to.Raid)
Comment on lines +128 to +133
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there precedence for other sugar we have in Butane which ends up having to relist every other section at the level it's operating at?

for i, file := range from.Files {
if util.NotEmpty(file.Parent.Path) {
var dir string

yamlPath := path.New("yaml", "files", i, "parent")

if !strings.Contains(file.Path, *file.Parent.Path) {
r.AddOnError(yamlPath, common.ErrInvalidParent)
continue
}

dir = filepath.Dir(file.Path)
dir = filepath.ToSlash(dir)

fmt.Println("Final Directory:", dir)

for dir != "" {
renderedDir := types.Directory{
Node: types.Node{
Path: dir,
Group: types.NodeGroup{ID: file.Group.ID, Name: file.Group.Name},
User: types.NodeUser{ID: file.User.ID, Name: file.User.Name},
},
DirectoryEmbedded1: types.DirectoryEmbedded1{
Mode: file.Parent.Mode,
},
}
to.Directories = append(to.Directories, renderedDir)
nextDir, _ := filepath.Split(dir)
// make sure to clean the path to avoid consistency issues

if dir == *file.Parent.Path || nextDir == dir {
// we have reached the parent directory or the end of the path
break
}
// Remove trailing / for consistency on matching directories
dir = strings.TrimSuffix(nextDir, "/")
dir = filepath.ToSlash(dir)
}
tm.AddFromCommonSource(yamlPath, path.New("json", "directories"), to.Directories)
}

}
return
}

func translateFile(from File, options common.TranslateOptions) (to types.File, tm translate.TranslationSet, r report.Report) {
tr := translate.NewTranslator("yaml", "json", options)
tr.AddCustomTranslator(translateResource)
Expand All @@ -134,6 +190,22 @@ func translateFile(from File, options common.TranslateOptions) (to types.File, t
return
}

func translateLuks(from Luks, options common.TranslateOptions) (to types.Luks, tm translate.TranslationSet, r report.Report) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird to me that we need to rewrite this since IIUC nothing in what we do is related to LUKS (right?).

tr := translate.NewTranslator("yaml", "json", options)
tr.AddCustomTranslator(translateResource)
tm, r = translate.Prefixed(tr, "clevis", &from.Clevis, &to.Clevis)
translate.MergeP(tr, tm, &r, "device", &from.Device, &to.Device)
translate.MergeP(tr, tm, &r, "discard", &from.Discard, &to.Discard)
translate.MergeP2(tr, tm, &r, "key_file", &from.KeyFile, "keyFile", &to.KeyFile)
translate.MergeP(tr, tm, &r, "label", &from.Label, &to.Label)
translate.MergeP(tr, tm, &r, "name", &from.Name, &to.Name)
translate.MergeP2(tr, tm, &r, "open_options", &from.OpenOptions, "openOptions", &to.OpenOptions)
translate.MergeP(tr, tm, &r, "options", &from.Options, &to.Options)
translate.MergeP(tr, tm, &r, "uuid", &from.UUID, &to.UUID)
translate.MergeP2(tr, tm, &r, "wipe_volume", &from.WipeVolume, "wipeVolume", &to.WipeVolume)
return
}

func translateResource(from Resource, options common.TranslateOptions) (to types.Resource, tm translate.TranslationSet, r report.Report) {
tr := translate.NewTranslator("yaml", "json", options)
tm, r = translate.Prefixed(tr, "verification", &from.Verification, &to.Verification)
Expand Down Expand Up @@ -294,7 +366,7 @@ func (c Config) processTrees(ret *types.Config, options common.TranslateOptions)
return ts, r
}
t := newNodeTracker(ret)

ts.AddTranslation(path.New("yaml", "storage"), path.New("json", "storage"))
for i, tree := range c.Storage.Trees {
yamlPath := path.New("yaml", "storage", "trees", i)
if options.FilesDir == "" {
Expand Down
189 changes: 189 additions & 0 deletions base/v0_6_exp/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,195 @@ func TestTranslateFile(t *testing.T) {
})
}
}
func TestTranslateStorage(t *testing.T) {
tests := []struct {
in Storage
out types.Storage
errPath path.ContextPath
errors error
skip func(t *testing.T)
}{
// Basic parent file directory
{
in: Storage{
Files: []File{
{
Path: "/foo/bar/txt.txt",
Contents: Resource{},
Mode: util.IntToPtr(420),
Parent: Parent{
Path: util.StrToPtr("/foo"),
Mode: util.IntToPtr(420),
},
},
},
},
out: types.Storage{
Files: []types.File{
{
Node: types.Node{
Path: "/foo/bar/txt.txt",
},
FileEmbedded1: types.FileEmbedded1{
Mode: util.IntToPtr(420),
Contents: types.Resource{},
},
},
},
Directories: []types.Directory{
{
Node: types.Node{
Path: "/foo/bar",
},
DirectoryEmbedded1: types.DirectoryEmbedded1{
Mode: util.IntToPtr(420),
},
},
{
Node: types.Node{
Path: "/foo",
},
DirectoryEmbedded1: types.DirectoryEmbedded1{
Mode: util.IntToPtr(420),
},
},
},
},
errPath: path.ContextPath{},
errors: nil,
skip: func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on Windows")
}
},
},
// Empty parent file directory
{
in: Storage{
Files: []File{
{
Path: "/foo/bar/txt.txt",
Contents: Resource{},
Mode: util.IntToPtr(420),
Parent: Parent{
Path: util.StrToPtr(""),
Mode: util.IntToPtr(420),
},
},
},
},
out: types.Storage{
Files: []types.File{
{
Node: types.Node{
Path: "/foo/bar/txt.txt",
},
FileEmbedded1: types.FileEmbedded1{
Mode: util.IntToPtr(420),
Contents: types.Resource{},
},
},
},
},
errPath: path.ContextPath{},
errors: nil,
skip: func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on Windows")
}
},
},
// Parent not defined
{
in: Storage{
Files: []File{
{
Path: "/foo/bar/txt.txt",
Contents: Resource{},
Mode: util.IntToPtr(420),
},
},
},
out: types.Storage{
Files: []types.File{
{
Node: types.Node{
Path: "/foo/bar/txt.txt",
},
FileEmbedded1: types.FileEmbedded1{
Mode: util.IntToPtr(420),
Contents: types.Resource{},
},
},
},
},
errPath: path.ContextPath{},
errors: nil,
skip: func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on Windows")
}
},
},
// Parent path is not related to file path
{
in: Storage{
Files: []File{
{
Path: "/foo/bar/txt.txt",
Contents: Resource{},
Mode: util.IntToPtr(420),
Parent: Parent{
Path: util.StrToPtr("/godzilla"),
Mode: util.IntToPtr(420),
},
},
},
},
out: types.Storage{
Files: []types.File{
{
Node: types.Node{
Path: "/foo/bar/txt.txt",
},
FileEmbedded1: types.FileEmbedded1{
Mode: util.IntToPtr(420),
Contents: types.Resource{},
},
},
},
},
errPath: path.New("yaml", "files", 0, "parent"),
errors: common.ErrInvalidParent,
skip: func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on Windows")
}
},
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) {
if test.skip != nil {
// give the test an opportunity to skip
test.skip(t)
}
actual, translations, r := translateStorage(test.in, common.TranslateOptions{})
r = confutil.TranslateReportPaths(r, translations)
baseutil.VerifyReport(t, test.in, r)
assert.Equal(t, test.out, actual, "translation mismatch")
assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage")
if test.errors != nil {
expected := report.Report{}
expected.AddOnError(test.errPath, test.errors)
assert.Equal(t, expected, r, "bad report for test case %d", i)
} else {
assert.Equal(t, report.Report{}, r, "non-empty report")
}
})
}
}

// TestTranslateDirectory tests translating the ct storage.directories.[i] entries to ignition storage.directories.[i] entires.
func TestTranslateDirectory(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions config/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ var (
ErrLinkSupport = errors.New("links are not supported in this spec version")
ErrLuksSupport = errors.New("luks is not supported in this spec version")
ErrRaidSupport = errors.New("raid is not supported in this spec version")
ErrInvalidParent = errors.New("parent must be included in the file path")

// Grub
ErrGrubUserNameNotSpecified = errors.New("field \"name\" is required")
Expand Down
3 changes: 3 additions & 0 deletions docs/config-fcos-v1_6-exp.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s
* **_group_** (object): specifies the file's group.
* **_id_** (integer): the group ID of the group.
* **_name_** (string): the group name of the group.
* **_parent_** (object): the parent directory for the specified file, by declaring a parent the directories from the parent to the file's target destination.
* **_path_** (string): the path of the directory within the file's 'path'.
* **_mode_** (integer): directory modes are set to 0755 as a default if not specified and directory does not exist prior to the specified file.
* **_directories_** (list of objects): the list of directories to be created. Every file, directory, and link must have a unique `path`.
* **path** (string): the absolute path to the directory.
* **_overwrite_** (boolean): whether to delete preexisting nodes at the path. If false and a directory already exists at the path, Ignition will only set its permissions. If false and a non-directory exists at that path, Ignition will fail. Defaults to false.
Expand Down
3 changes: 3 additions & 0 deletions docs/config-fiot-v1_1-exp.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ The Fedora IoT configuration is a YAML document conforming to the following spec
* **_group_** (object): specifies the file's group.
* **_id_** (integer): the group ID of the group.
* **_name_** (string): the group name of the group.
* **_parent_** (object): the parent directory for the specified file, by declaring a parent the directories from the parent to the file's target destination.
* **_path_** (string): the path of the directory within the file's 'path'.
* **_mode_** (integer): directory modes are set to 0755 as a default if not specified and directory does not exist prior to the specified file.
* **_directories_** (list of objects): the list of directories to be created. Every file, directory, and link must have a unique `path`.
* **path** (string): the absolute path to the directory.
* **_overwrite_** (boolean): whether to delete preexisting nodes at the path. If false and a directory already exists at the path, Ignition will only set its permissions. If false and a non-directory exists at that path, Ignition will fail. Defaults to false.
Expand Down
3 changes: 3 additions & 0 deletions docs/config-flatcar-v1_2-exp.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ The Flatcar configuration is a YAML document conforming to the following specifi
* **_group_** (object): specifies the file's group.
* **_id_** (integer): the group ID of the group.
* **_name_** (string): the group name of the group.
* **_parent_** (object): the parent directory for the specified file, by declaring a parent the directories from the parent to the file's target destination.
* **_path_** (string): the path of the directory within the file's 'path'.
* **_mode_** (integer): directory modes are set to 0755 as a default if not specified and directory does not exist prior to the specified file.
* **_directories_** (list of objects): the list of directories to be created. Every file, directory, and link must have a unique `path`.
* **path** (string): the absolute path to the directory.
* **_overwrite_** (boolean): whether to delete preexisting nodes at the path. If false and a directory already exists at the path, Ignition will only set its permissions. If false and a non-directory exists at that path, Ignition will fail. Defaults to false.
Expand Down
3 changes: 3 additions & 0 deletions docs/config-openshift-v4_17-exp.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ The OpenShift configuration is a YAML document conforming to the following speci
* **_group_** (object): specifies the file's group.
* **_id_** (integer): the group ID of the group.
* **_name_** (string): the group name of the group.
* **_parent_** (object): the parent directory for the specified file, by declaring a parent the directories from the parent to the file's target destination.
* **_path_** (string): the path of the directory within the file's 'path'.
* **_mode_** (integer): directory modes are set to 0755 as a default if not specified and directory does not exist prior to the specified file.
* **_luks_** (list of objects): the list of luks devices to be created. Every device must have a unique `name`.
* **name** (string): the name of the luks device.
* **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks.
Expand Down
3 changes: 3 additions & 0 deletions docs/config-r4e-v1_2-exp.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ The RHEL for Edge configuration is a YAML document conforming to the following s
* **_group_** (object): specifies the file's group.
* **_id_** (integer): the group ID of the group.
* **_name_** (string): the group name of the group.
* **_parent_** (object): the parent directory for the specified file, by declaring a parent the directories from the parent to the file's target destination.
* **_path_** (string): the path of the directory within the file's 'path'.
* **_mode_** (integer): directory modes are set to 0755 as a default if not specified and directory does not exist prior to the specified file.
* **_directories_** (list of objects): the list of directories to be created. Every file, directory, and link must have a unique `path`.
* **path** (string): the absolute path to the directory.
* **_overwrite_** (boolean): whether to delete preexisting nodes at the path. If false and a directory already exists at the path, Ignition will only set its permissions. If false and a non-directory exists at that path, Ignition will fail. Defaults to false.
Expand Down
Loading
Loading