diff --git a/base/v0_7_exp/schema.go b/base/v0_7_exp/schema.go index ecdeff1e4..4f4e05ed3 100644 --- a/base/v0_7_exp/schema.go +++ b/base/v0_7_exp/schema.go @@ -249,8 +249,12 @@ type Timeouts struct { } type Tree struct { - Local string `yaml:"local"` - Path *string `yaml:"path"` + Group NodeGroup `yaml:"group"` + Local string `yaml:"local"` + Path *string `yaml:"path"` + User NodeUser `yaml:"user"` + FileMode *int `yaml:"file_mode"` + DirMode *int `yaml:"dir_mode"` } type Unit struct { diff --git a/base/v0_7_exp/translate.go b/base/v0_7_exp/translate.go index fd95720d4..208826752 100644 --- a/base/v0_7_exp/translate.go +++ b/base/v0_7_exp/translate.go @@ -333,29 +333,69 @@ func (c Config) processTrees(ret *types.Config, options common.TranslateOptions) destBaseDir = *tree.Path } - walkTree(yamlPath, &ts, &r, t, srcBaseDir, destBaseDir, options) + walkTree(yamlPath, &ts, &r, t, treeWalkOptions{ + srcBaseDir: srcBaseDir, + destBaseDir: destBaseDir, + TranslateOptions: options, + user: tree.User, + group: tree.Group, + fileMode: tree.FileMode, + dirMode: tree.DirMode, + }) } return ts, r } -func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report.Report, t *nodeTracker, srcBaseDir, destBaseDir string, options common.TranslateOptions) { +type treeWalkOptions struct { + srcBaseDir string + destBaseDir string + common.TranslateOptions + user NodeUser + group NodeGroup + fileMode *int + dirMode *int +} + +func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report.Report, t *nodeTracker, options treeWalkOptions) { // The strategy for errors within WalkFunc is to add an error to // the report and return nil, so walking continues but translation // will fail afterward. - err := filepath.Walk(srcBaseDir, func(srcPath string, info os.FileInfo, err error) error { + err := filepath.Walk(options.srcBaseDir, func(srcPath string, info os.FileInfo, err error) error { if err != nil { r.AddOnError(yamlPath, err) return nil } - relPath, err := filepath.Rel(srcBaseDir, srcPath) + relPath, err := filepath.Rel(options.srcBaseDir, srcPath) if err != nil { r.AddOnError(yamlPath, err) return nil } - destPath := slashpath.Join(destBaseDir, filepath.ToSlash(relPath)) + destPath := slashpath.Join(options.destBaseDir, filepath.ToSlash(relPath)) if info.Mode().IsDir() { - return nil + // If nothing custom is required we skip directories generation + if options.dirMode == nil && options.user == (NodeUser{}) && options.group == (NodeGroup{}) { + return nil + } + + if t.Exists(destPath) { + r.AddOnError(yamlPath, common.ErrNodeExists) + return nil + } + mode := util.IntToPtr(0755) + if options.dirMode != nil { + mode = options.dirMode + } + i, dir := t.AddDir(types.Directory{ + Node: createNode(destPath, options.user, options.group), + DirectoryEmbedded1: types.DirectoryEmbedded1{ + Mode: mode, + }, + }) + ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "directories", i), dir) + if i == 0 { + ts.AddTranslation(yamlPath, path.New("json", "storage", "directories")) + } } else if info.Mode().IsRegular() { i, file := t.GetFile(destPath) if file != nil { @@ -369,9 +409,7 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report return nil } i, file = t.AddFile(types.File{ - Node: types.Node{ - Path: destPath, - }, + Node: createNode(destPath, options.user, options.group), }) ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "files", i), file) if i == 0 { @@ -400,6 +438,9 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report if info.Mode()&0111 != 0 { mode = 0755 } + if options.fileMode != nil { + mode = *options.fileMode + } file.Mode = &mode ts.AddTranslation(yamlPath, path.New("json", "storage", "files", i, "mode")) } @@ -416,9 +457,7 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report return nil } i, link = t.AddLink(types.Link{ - Node: types.Node{ - Path: destPath, - }, + Node: createNode(destPath, options.user, options.group), }) ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "links", i), link) if i == 0 { @@ -441,6 +480,20 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report r.AddOnError(yamlPath, err) } +func createNode(destPath string, user NodeUser, group NodeGroup) types.Node { + return types.Node{ + Path: destPath, + User: types.NodeUser{ + ID: user.ID, + Name: user.Name, + }, + Group: types.NodeGroup{ + ID: group.ID, + Name: group.Name, + }, + } +} + func (c Config) addMountUnits(config *types.Config, ts *translate.TranslationSet) { if len(c.Storage.Filesystems) == 0 { return diff --git a/base/v0_7_exp/translate_test.go b/base/v0_7_exp/translate_test.go index 0433fad35..052136321 100644 --- a/base/v0_7_exp/translate_test.go +++ b/base/v0_7_exp/translate_test.go @@ -1212,6 +1212,7 @@ func TestTranslateTree(t *testing.T) { inDirs []Directory inLinks []Link outFiles []types.File + outDirs []types.Directory outLinks []types.Link report string skip func(t *testing.T) @@ -1643,6 +1644,160 @@ func TestTranslateTree(t *testing.T) { report: "error at $.storage.trees.0: " + common.ErrTreeNotDirectory.Error() + "\n" + "error at $.storage.trees.1: " + osStatName + " %FilesDir%" + string(filepath.Separator) + "nonexistent: " + osNotFound + "\n", }, + // Permissions and ownership + { + dirFiles: map[string]os.FileMode{ + "tree/file": 0600, + "tree/subdir/file": 0644, + "tree2/file": 0600, + }, + dirLinks: map[string]string{ + "tree/subdir/link": "../file", + }, + inTrees: []Tree{ + { + Local: "tree", + FileMode: util.IntToPtr(0777), + User: NodeUser{ + Name: util.StrToPtr("bovik"), + }, + Group: NodeGroup{ + ID: util.IntToPtr(1000), + }, + }, + { + Local: "tree2", + DirMode: util.IntToPtr(0777), + Path: util.StrToPtr("/etc"), + }, + }, + outDirs: []types.Directory{ + { + Node: types.Node{ + Group: types.NodeGroup{ + ID: util.IntToPtr(1000), + }, + Path: "/", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + DirectoryEmbedded1: types.DirectoryEmbedded1{ + Mode: util.IntToPtr(0755), + }, + }, + { + Node: types.Node{ + Group: types.NodeGroup{ + ID: util.IntToPtr(1000), + }, + Path: "/subdir", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + DirectoryEmbedded1: types.DirectoryEmbedded1{ + Mode: util.IntToPtr(0755), + }, + }, + { + Node: types.Node{ + Path: "/etc", + }, + DirectoryEmbedded1: types.DirectoryEmbedded1{ + Mode: util.IntToPtr(0777), + }, + }, + }, + outFiles: []types.File{ + { + Node: types.Node{ + Path: "/file", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + Group: types.NodeGroup{ + ID: util.IntToPtr(1000), + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0777), + }, + }, + { + Node: types.Node{ + Path: "/subdir/file", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + Group: types.NodeGroup{ + ID: util.IntToPtr(1000), + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Fsubdir%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0777), + }, + }, + { + Node: types.Node{ + Path: "/etc/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree2%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0644), + }, + }, + }, + outLinks: []types.Link{ + { + Node: types.Node{ + Path: "/subdir/link", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + Group: types.NodeGroup{ + ID: util.IntToPtr(1000), + }, + }, + LinkEmbedded1: types.LinkEmbedded1{ + Target: util.StrToPtr("../file"), + }, + }, + }, + }, + // Overwrite via tree ownership fails + { + dirFiles: map[string]os.FileMode{ + "tree/etc/file": 0600, + }, + inDirs: []Directory{ + {Path: "/etc"}, + }, + inTrees: []Tree{ + { + Local: "tree", + FileMode: util.IntToPtr(0777), + User: NodeUser{ + Name: util.StrToPtr("bovik"), + }, + Group: NodeGroup{ + ID: util.IntToPtr(1000), + }, + }, + }, + report: "error at $.storage.trees.0: " + common.ErrNodeExists.Error() + "\n", + }, } for i, test := range tests { @@ -1728,7 +1883,7 @@ func TestTranslateTree(t *testing.T) { assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") assert.Equal(t, test.outFiles, actual.Storage.Files, "files mismatch") - assert.Equal(t, []types.Directory(nil), actual.Storage.Directories, "directories mismatch") + assert.Equal(t, test.outDirs, actual.Storage.Directories, "directories mismatch") assert.Equal(t, test.outLinks, actual.Storage.Links, "links mismatch") }) } diff --git a/docs/config-fcos-v1_7-exp.md b/docs/config-fcos-v1_7-exp.md index de740ada4..e5e45afff 100644 --- a/docs/config-fcos-v1_7-exp.md +++ b/docs/config-fcos-v1_7-exp.md @@ -170,9 +170,17 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s * **_needs_network_** (boolean): whether or not the device requires networking. * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. * **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false. - * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership, file modes (using `file_mode`) and directories modes (using `dir_mode`) can be specified for the tree. If not specified, ownership is not preserved and file modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. + * **_file_mode_** (integer): Custom permissions to apply to files + * **_dir_mode_** (integer): Custom permissions to apply to directories + * **_user_** (object): User owner of the tree + * **_name_** (string): username + * **_id_** (integer): uid + * **_group_** (object): Group owner of the tree + * **_name_** (string): group name + * **_id_** (integer): gid * **_systemd_** (object): describes the desired state of the systemd units. * **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`. * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). diff --git a/docs/config-fiot-v1_1-exp.md b/docs/config-fiot-v1_1-exp.md index 3e8c33fb4..32118e519 100644 --- a/docs/config-fiot-v1_1-exp.md +++ b/docs/config-fiot-v1_1-exp.md @@ -109,9 +109,17 @@ The Fedora IoT configuration is a YAML document conforming to the following spec * **_name_** (string): the group name of the group. * **target** (string): the target path of the link * **_hard_** (boolean): a symbolic link is created if this is false, a hard one if this is true. - * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership, file modes (using `file_mode`) and directories modes (using `dir_mode`) can be specified for the tree. If not specified, ownership is not preserved and file modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. + * **_file_mode_** (integer): Custom permissions to apply to files + * **_dir_mode_** (integer): Custom permissions to apply to directories + * **_user_** (object): User owner of the tree + * **_name_** (string): username + * **_id_** (integer): uid + * **_group_** (object): Group owner of the tree + * **_name_** (string): group name + * **_id_** (integer): gid * **_systemd_** (object): describes the desired state of the systemd units. * **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`. * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). diff --git a/docs/config-flatcar-v1_2-exp.md b/docs/config-flatcar-v1_2-exp.md index 806556b43..c46b0558e 100644 --- a/docs/config-flatcar-v1_2-exp.md +++ b/docs/config-flatcar-v1_2-exp.md @@ -168,9 +168,17 @@ The Flatcar configuration is a YAML document conforming to the following specifi * **pin** (string): the clevis pin. * **config** (string): the clevis configuration JSON. * **_needs_network_** (boolean): whether or not the device requires networking. - * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership, file modes (using `file_mode`) and directories modes (using `dir_mode`) can be specified for the tree. If not specified, ownership is not preserved and file modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. + * **_file_mode_** (integer): Custom permissions to apply to files + * **_dir_mode_** (integer): Custom permissions to apply to directories + * **_user_** (object): User owner of the tree + * **_name_** (string): username + * **_id_** (integer): uid + * **_group_** (object): Group owner of the tree + * **_name_** (string): group name + * **_id_** (integer): gid * **_systemd_** (object): describes the desired state of the systemd units. * **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`. * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). diff --git a/docs/config-openshift-v4_21-exp.md b/docs/config-openshift-v4_21-exp.md index 91439e8d6..80667622e 100644 --- a/docs/config-openshift-v4_21-exp.md +++ b/docs/config-openshift-v4_21-exp.md @@ -139,9 +139,17 @@ The OpenShift configuration is a YAML document conforming to the following speci * **_needs_network_** (boolean): whether or not the device requires networking. * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. * **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false. - * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Symlinks must not be present. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. File attributes can be overridden by creating a corresponding entry in the `files` section; such entries must omit `contents`. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Symlinks must not be present. Ownership, file modes (using `file_mode`) and directories modes (using `dir_mode`) can be specified for the tree. If not specified, ownership is not preserved and file modes are set to 0755 if the local file is executable or 0644 otherwise. File attributes can be overridden by creating a corresponding entry in the `files` section; such entries must omit `contents`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. + * **_file_mode_** (integer): Custom permissions to apply to files + * **_dir_mode_** (integer): Custom permissions to apply to directories + * **_user_** (object): User owner of the tree + * **_name_** (string): username + * **_id_** (integer): uid + * **_group_** (object): Group owner of the tree + * **_name_** (string): group name + * **_id_** (integer): gid * **_systemd_** (object): describes the desired state of the systemd units. * **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`. * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). diff --git a/docs/config-r4e-v1_2-exp.md b/docs/config-r4e-v1_2-exp.md index 9f309af0a..0d4b15ed4 100644 --- a/docs/config-r4e-v1_2-exp.md +++ b/docs/config-r4e-v1_2-exp.md @@ -109,9 +109,17 @@ The RHEL for Edge configuration is a YAML document conforming to the following s * **_name_** (string): the group name of the group. * **target** (string): the target path of the link * **_hard_** (boolean): a symbolic link is created if this is false, a hard one if this is true. - * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership, file modes (using `file_mode`) and directories modes (using `dir_mode`) can be specified for the tree. If not specified, ownership is not preserved and file modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. + * **_file_mode_** (integer): Custom permissions to apply to files + * **_dir_mode_** (integer): Custom permissions to apply to directories + * **_user_** (object): User owner of the tree + * **_name_** (string): username + * **_id_** (integer): uid + * **_group_** (object): Group owner of the tree + * **_name_** (string): group name + * **_id_** (integer): gid * **_systemd_** (object): describes the desired state of the systemd units. * **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`. * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). diff --git a/docs/release-notes.md b/docs/release-notes.md index de9438318..681e36c01 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,8 @@ nav_order: 9 ### Features +- Add support for mode and ownership settings for trees. + ### Bug fixes - Warn for `boot_device.layout` to be specified when using `boot_device.mirror` _(fcos 1.3.0-1.6.0)_ diff --git a/internal/doc/butane.yaml b/internal/doc/butane.yaml index 4ef841287..cbdcf1997 100644 --- a/internal/doc/butane.yaml +++ b/internal/doc/butane.yaml @@ -256,9 +256,9 @@ root: use: mode - name: trees after: $ - desc: a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. + desc: a list of local directory trees to be embedded in the config. Ownership, file modes (using `file_mode`) and directories modes (using `dir_mode`) can be specified for the tree. If not specified, ownership is not preserved and file modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. transforms: - - regex: Ownership is not preserved. + - regex: Ownership, replacement: Symlinks must not be present. $0 if: - variant: openshift @@ -274,11 +274,56 @@ root: replacement: $1. if: - variant: openshift + - regex: ", file modes \\(using `file_mode`\\) and directories modes \\(using `dir_mode`\\) can be specified for the tree. If not specified, ownership is not preserved and f" + replacement: " is not preserved. F" + if: + - variant: fcos + max: 1.6.0 + - variant: fiot + max: 1.0.0 + - variant: flatcar + max: 1.1.0 + - variant: openshift + max: 4.20.0 + - variant: r4e + max: 1.1.0 children: - name: local desc: the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. - name: path desc: the path of the tree within the target system. Defaults to `/`. + - name: file_mode + desc: Custom permissions to apply to files + - name: dir_mode + desc: Custom permissions to apply to directories + transforms: + - regex: ".*" + replacement: "Unsupported" + if: + - variant: fcos + max: 1.6.0 + - variant: fiot + max: 1.0.0 + - variant: flatcar + max: 1.1.0 + - variant: openshift + max: 4.20.0 + - variant: r4e + max: 1.1.0 + - name: user + desc: User owner of the tree + children: + - name: name + desc: username + - name: id + desc: uid + - name: group + desc: Group owner of the tree + children: + - name: name + desc: group name + - name: id + desc: gid - name: systemd children: - name: units