Skip to content

Commit d9d10bc

Browse files
committed
base/v0_7_exp: add ownership and mode support for trees
Add support for setting user, group, file_mode, and dir_mode on trees to address the use case of deploying directory trees with specific ownership for rootless containers. Fixes: #544
1 parent a87fa70 commit d9d10bc

File tree

10 files changed

+294
-22
lines changed

10 files changed

+294
-22
lines changed

base/v0_7_exp/schema.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,12 @@ type Timeouts struct {
249249
}
250250

251251
type Tree struct {
252-
Local string `yaml:"local"`
253-
Path *string `yaml:"path"`
252+
Group NodeGroup `yaml:"group"`
253+
Local string `yaml:"local"`
254+
Path *string `yaml:"path"`
255+
User NodeUser `yaml:"user"`
256+
FileMode *int `yaml:"file_mode"`
257+
DirMode *int `yaml:"dir_mode"`
254258
}
255259

256260
type Unit struct {

base/v0_7_exp/translate.go

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -333,29 +333,69 @@ func (c Config) processTrees(ret *types.Config, options common.TranslateOptions)
333333
destBaseDir = *tree.Path
334334
}
335335

336-
walkTree(yamlPath, &ts, &r, t, srcBaseDir, destBaseDir, options)
336+
walkTree(yamlPath, &ts, &r, t, treeWalkOptions{
337+
srcBaseDir: srcBaseDir,
338+
destBaseDir: destBaseDir,
339+
TranslateOptions: options,
340+
user: tree.User,
341+
group: tree.Group,
342+
fileMode: tree.FileMode,
343+
dirMode: tree.DirMode,
344+
})
337345
}
338346
return ts, r
339347
}
340348

341-
func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report.Report, t *nodeTracker, srcBaseDir, destBaseDir string, options common.TranslateOptions) {
349+
type treeWalkOptions struct {
350+
srcBaseDir string
351+
destBaseDir string
352+
common.TranslateOptions
353+
user NodeUser
354+
group NodeGroup
355+
fileMode *int
356+
dirMode *int
357+
}
358+
359+
func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report.Report, t *nodeTracker, options treeWalkOptions) {
342360
// The strategy for errors within WalkFunc is to add an error to
343361
// the report and return nil, so walking continues but translation
344362
// will fail afterward.
345-
err := filepath.Walk(srcBaseDir, func(srcPath string, info os.FileInfo, err error) error {
363+
err := filepath.Walk(options.srcBaseDir, func(srcPath string, info os.FileInfo, err error) error {
346364
if err != nil {
347365
r.AddOnError(yamlPath, err)
348366
return nil
349367
}
350-
relPath, err := filepath.Rel(srcBaseDir, srcPath)
368+
relPath, err := filepath.Rel(options.srcBaseDir, srcPath)
351369
if err != nil {
352370
r.AddOnError(yamlPath, err)
353371
return nil
354372
}
355-
destPath := slashpath.Join(destBaseDir, filepath.ToSlash(relPath))
373+
destPath := slashpath.Join(options.destBaseDir, filepath.ToSlash(relPath))
356374

357375
if info.Mode().IsDir() {
358-
return nil
376+
// If nothing custom is required we skip directories generation
377+
if options.dirMode == nil && options.user == (NodeUser{}) && options.group == (NodeGroup{}) {
378+
return nil
379+
}
380+
381+
if t.Exists(destPath) {
382+
r.AddOnError(yamlPath, common.ErrNodeExists)
383+
return nil
384+
}
385+
mode := util.IntToPtr(0755)
386+
if options.dirMode != nil {
387+
mode = options.dirMode
388+
}
389+
i, dir := t.AddDir(types.Directory{
390+
Node: createNode(destPath, options.user, options.group),
391+
DirectoryEmbedded1: types.DirectoryEmbedded1{
392+
Mode: mode,
393+
},
394+
})
395+
ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "directories", i), dir)
396+
if i == 0 {
397+
ts.AddTranslation(yamlPath, path.New("json", "storage", "directories"))
398+
}
359399
} else if info.Mode().IsRegular() {
360400
i, file := t.GetFile(destPath)
361401
if file != nil {
@@ -369,9 +409,7 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report
369409
return nil
370410
}
371411
i, file = t.AddFile(types.File{
372-
Node: types.Node{
373-
Path: destPath,
374-
},
412+
Node: createNode(destPath, options.user, options.group),
375413
})
376414
ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "files", i), file)
377415
if i == 0 {
@@ -400,6 +438,9 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report
400438
if info.Mode()&0111 != 0 {
401439
mode = 0755
402440
}
441+
if options.fileMode != nil {
442+
mode = *options.fileMode
443+
}
403444
file.Mode = &mode
404445
ts.AddTranslation(yamlPath, path.New("json", "storage", "files", i, "mode"))
405446
}
@@ -416,9 +457,7 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report
416457
return nil
417458
}
418459
i, link = t.AddLink(types.Link{
419-
Node: types.Node{
420-
Path: destPath,
421-
},
460+
Node: createNode(destPath, options.user, options.group),
422461
})
423462
ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "links", i), link)
424463
if i == 0 {
@@ -441,6 +480,20 @@ func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report
441480
r.AddOnError(yamlPath, err)
442481
}
443482

483+
func createNode(destPath string, user NodeUser, group NodeGroup) types.Node {
484+
return types.Node{
485+
Path: destPath,
486+
User: types.NodeUser{
487+
ID: user.ID,
488+
Name: user.Name,
489+
},
490+
Group: types.NodeGroup{
491+
ID: group.ID,
492+
Name: group.Name,
493+
},
494+
}
495+
}
496+
444497
func (c Config) addMountUnits(config *types.Config, ts *translate.TranslationSet) {
445498
if len(c.Storage.Filesystems) == 0 {
446499
return

base/v0_7_exp/translate_test.go

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,7 @@ func TestTranslateTree(t *testing.T) {
12121212
inDirs []Directory
12131213
inLinks []Link
12141214
outFiles []types.File
1215+
outDirs []types.Directory
12151216
outLinks []types.Link
12161217
report string
12171218
skip func(t *testing.T)
@@ -1643,6 +1644,160 @@ func TestTranslateTree(t *testing.T) {
16431644
report: "error at $.storage.trees.0: " + common.ErrTreeNotDirectory.Error() + "\n" +
16441645
"error at $.storage.trees.1: " + osStatName + " %FilesDir%" + string(filepath.Separator) + "nonexistent: " + osNotFound + "\n",
16451646
},
1647+
// Permissions and ownership
1648+
{
1649+
dirFiles: map[string]os.FileMode{
1650+
"tree/file": 0600,
1651+
"tree/subdir/file": 0644,
1652+
"tree2/file": 0600,
1653+
},
1654+
dirLinks: map[string]string{
1655+
"tree/subdir/link": "../file",
1656+
},
1657+
inTrees: []Tree{
1658+
{
1659+
Local: "tree",
1660+
FileMode: util.IntToPtr(0777),
1661+
User: NodeUser{
1662+
Name: util.StrToPtr("bovik"),
1663+
},
1664+
Group: NodeGroup{
1665+
ID: util.IntToPtr(1000),
1666+
},
1667+
},
1668+
{
1669+
Local: "tree2",
1670+
DirMode: util.IntToPtr(0777),
1671+
Path: util.StrToPtr("/etc"),
1672+
},
1673+
},
1674+
outDirs: []types.Directory{
1675+
{
1676+
Node: types.Node{
1677+
Group: types.NodeGroup{
1678+
ID: util.IntToPtr(1000),
1679+
},
1680+
Path: "/",
1681+
User: types.NodeUser{
1682+
Name: util.StrToPtr("bovik"),
1683+
},
1684+
},
1685+
DirectoryEmbedded1: types.DirectoryEmbedded1{
1686+
Mode: util.IntToPtr(0755),
1687+
},
1688+
},
1689+
{
1690+
Node: types.Node{
1691+
Group: types.NodeGroup{
1692+
ID: util.IntToPtr(1000),
1693+
},
1694+
Path: "/subdir",
1695+
User: types.NodeUser{
1696+
Name: util.StrToPtr("bovik"),
1697+
},
1698+
},
1699+
DirectoryEmbedded1: types.DirectoryEmbedded1{
1700+
Mode: util.IntToPtr(0755),
1701+
},
1702+
},
1703+
{
1704+
Node: types.Node{
1705+
Path: "/etc",
1706+
},
1707+
DirectoryEmbedded1: types.DirectoryEmbedded1{
1708+
Mode: util.IntToPtr(0777),
1709+
},
1710+
},
1711+
},
1712+
outFiles: []types.File{
1713+
{
1714+
Node: types.Node{
1715+
Path: "/file",
1716+
User: types.NodeUser{
1717+
Name: util.StrToPtr("bovik"),
1718+
},
1719+
Group: types.NodeGroup{
1720+
ID: util.IntToPtr(1000),
1721+
},
1722+
},
1723+
FileEmbedded1: types.FileEmbedded1{
1724+
Contents: types.Resource{
1725+
Source: util.StrToPtr("data:,tree%2Ffile"),
1726+
Compression: util.StrToPtr(""),
1727+
},
1728+
Mode: util.IntToPtr(0777),
1729+
},
1730+
},
1731+
{
1732+
Node: types.Node{
1733+
Path: "/subdir/file",
1734+
User: types.NodeUser{
1735+
Name: util.StrToPtr("bovik"),
1736+
},
1737+
Group: types.NodeGroup{
1738+
ID: util.IntToPtr(1000),
1739+
},
1740+
},
1741+
FileEmbedded1: types.FileEmbedded1{
1742+
Contents: types.Resource{
1743+
Source: util.StrToPtr("data:,tree%2Fsubdir%2Ffile"),
1744+
Compression: util.StrToPtr(""),
1745+
},
1746+
Mode: util.IntToPtr(0777),
1747+
},
1748+
},
1749+
{
1750+
Node: types.Node{
1751+
Path: "/etc/file",
1752+
},
1753+
FileEmbedded1: types.FileEmbedded1{
1754+
Contents: types.Resource{
1755+
Source: util.StrToPtr("data:,tree2%2Ffile"),
1756+
Compression: util.StrToPtr(""),
1757+
},
1758+
Mode: util.IntToPtr(0644),
1759+
},
1760+
},
1761+
},
1762+
outLinks: []types.Link{
1763+
{
1764+
Node: types.Node{
1765+
Path: "/subdir/link",
1766+
User: types.NodeUser{
1767+
Name: util.StrToPtr("bovik"),
1768+
},
1769+
Group: types.NodeGroup{
1770+
ID: util.IntToPtr(1000),
1771+
},
1772+
},
1773+
LinkEmbedded1: types.LinkEmbedded1{
1774+
Target: util.StrToPtr("../file"),
1775+
},
1776+
},
1777+
},
1778+
},
1779+
// Overwrite via tree ownership fails
1780+
{
1781+
dirFiles: map[string]os.FileMode{
1782+
"tree/etc/file": 0600,
1783+
},
1784+
inDirs: []Directory{
1785+
{Path: "/etc"},
1786+
},
1787+
inTrees: []Tree{
1788+
{
1789+
Local: "tree",
1790+
FileMode: util.IntToPtr(0777),
1791+
User: NodeUser{
1792+
Name: util.StrToPtr("bovik"),
1793+
},
1794+
Group: NodeGroup{
1795+
ID: util.IntToPtr(1000),
1796+
},
1797+
},
1798+
},
1799+
report: "error at $.storage.trees.0: " + common.ErrNodeExists.Error() + "\n",
1800+
},
16461801
}
16471802

16481803
for i, test := range tests {
@@ -1728,7 +1883,7 @@ func TestTranslateTree(t *testing.T) {
17281883
assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage")
17291884

17301885
assert.Equal(t, test.outFiles, actual.Storage.Files, "files mismatch")
1731-
assert.Equal(t, []types.Directory(nil), actual.Storage.Directories, "directories mismatch")
1886+
assert.Equal(t, test.outDirs, actual.Storage.Directories, "directories mismatch")
17321887
assert.Equal(t, test.outLinks, actual.Storage.Links, "links mismatch")
17331888
})
17341889
}

docs/config-fcos-v1_7-exp.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,17 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s
170170
* **_needs_network_** (boolean): whether or not the device requires networking.
171171
* **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device.
172172
* **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false.
173-
* **_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`.
173+
* **_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`.
174174
* **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument.
175175
* **_path_** (string): the path of the tree within the target system. Defaults to `/`.
176+
* **_file_mode_** (integer): Custom permissions to apply to files
177+
* **_dir_mode_** (integer): Custom permissions to apply to directories
178+
* **_user_** (object): User owner of the tree
179+
* **_name_** (string): username
180+
* **_id_** (integer): uid
181+
* **_group_** (object): Group owner of the tree
182+
* **_name_** (string): group name
183+
* **_id_** (integer): gid
176184
* **_systemd_** (object): describes the desired state of the systemd units.
177185
* **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`.
178186
* **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service").

docs/config-fiot-v1_1-exp.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,17 @@ The Fedora IoT configuration is a YAML document conforming to the following spec
109109
* **_name_** (string): the group name of the group.
110110
* **target** (string): the target path of the link
111111
* **_hard_** (boolean): a symbolic link is created if this is false, a hard one if this is true.
112-
* **_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`.
112+
* **_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`.
113113
* **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument.
114114
* **_path_** (string): the path of the tree within the target system. Defaults to `/`.
115+
* **_file_mode_** (integer): Custom permissions to apply to files
116+
* **_dir_mode_** (integer): Custom permissions to apply to directories
117+
* **_user_** (object): User owner of the tree
118+
* **_name_** (string): username
119+
* **_id_** (integer): uid
120+
* **_group_** (object): Group owner of the tree
121+
* **_name_** (string): group name
122+
* **_id_** (integer): gid
115123
* **_systemd_** (object): describes the desired state of the systemd units.
116124
* **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`.
117125
* **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service").

docs/config-flatcar-v1_2-exp.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,17 @@ The Flatcar configuration is a YAML document conforming to the following specifi
168168
* **pin** (string): the clevis pin.
169169
* **config** (string): the clevis configuration JSON.
170170
* **_needs_network_** (boolean): whether or not the device requires networking.
171-
* **_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`.
171+
* **_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`.
172172
* **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument.
173173
* **_path_** (string): the path of the tree within the target system. Defaults to `/`.
174+
* **_file_mode_** (integer): Custom permissions to apply to files
175+
* **_dir_mode_** (integer): Custom permissions to apply to directories
176+
* **_user_** (object): User owner of the tree
177+
* **_name_** (string): username
178+
* **_id_** (integer): uid
179+
* **_group_** (object): Group owner of the tree
180+
* **_name_** (string): group name
181+
* **_id_** (integer): gid
174182
* **_systemd_** (object): describes the desired state of the systemd units.
175183
* **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`.
176184
* **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service").

docs/config-openshift-v4_21-exp.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,17 @@ The OpenShift configuration is a YAML document conforming to the following speci
139139
* **_needs_network_** (boolean): whether or not the device requires networking.
140140
* **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device.
141141
* **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false.
142-
* **_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`.
142+
* **_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. Symlinks must not be present. 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`.
143143
* **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument.
144144
* **_path_** (string): the path of the tree within the target system. Defaults to `/`.
145+
* **_file_mode_** (integer): Custom permissions to apply to files
146+
* **_dir_mode_** (integer): Custom permissions to apply to directories
147+
* **_user_** (object): User owner of the tree
148+
* **_name_** (string): username
149+
* **_id_** (integer): uid
150+
* **_group_** (object): Group owner of the tree
151+
* **_name_** (string): group name
152+
* **_id_** (integer): gid
145153
* **_systemd_** (object): describes the desired state of the systemd units.
146154
* **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`.
147155
* **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service").

0 commit comments

Comments
 (0)