diff --git a/distrobuilder/main_incus.go b/distrobuilder/main_incus.go index c9c80b45..2b3b9d38 100644 --- a/distrobuilder/main_incus.go +++ b/distrobuilder/main_incus.go @@ -265,11 +265,35 @@ func (c *cmdIncus) run(cmd *cobra.Command, args []string, overlayDir string) err imageTargets |= shared.ImageTargetContainer } - for _, file := range c.global.definition.Files { + // Maps symbolic user/group names to their numeric IDs using information from passwd and group files. + userMap, groupMap, err := parsePasswdAndGroupFiles(overlayDir) + if err != nil { + c.global.logger.WithField("overlay", overlayDir).Warn("Could not parse passwd/group file: %w", err) + } + + for i, file := range c.global.definition.Files { if !shared.ApplyFilter(&file, c.global.definition.Image.Release, c.global.definition.Image.ArchitectureMapped, c.global.definition.Image.Variant, c.global.definition.Targets.Type, imageTargets) { continue } + if file.UID != "" && !isNumeric(file.UID) { + uid, exists := userMap[file.UID] + if exists { + c.global.definition.Files[i].UID = uid + } else { + c.global.logger.WithField("generator", file.Generator).Warnf("Could not find UID for user %q", file.UID) + } + } + + if file.UID != "" && !isNumeric(file.GID) { + gid, exists := groupMap[file.GID] + if exists { + c.global.definition.Files[i].GID = gid + } else { + c.global.logger.WithField("generator", file.Generator).Warnf("Could not find GID for group %q", file.GID) + } + } + generator, err := generators.Load(file.Generator, c.global.logger, c.global.flagCacheDir, overlayDir, file, *c.global.definition) if err != nil { return fmt.Errorf("Failed to load generator %q: %w", file.Generator, err) diff --git a/distrobuilder/passwd.go b/distrobuilder/passwd.go new file mode 100644 index 00000000..e2f1aed0 --- /dev/null +++ b/distrobuilder/passwd.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" +) + +// parsePasswdAndGroupFiles reads passwd and group files from the given root directory +// and returns mappings of names to IDs for both users and groups. +func parsePasswdAndGroupFiles(rootDir string) (map[string]string, map[string]string, error) { + userMap, err := parsePasswdFile(filepath.Join(rootDir, "/etc/passwd")) + if err != nil { + return nil, nil, fmt.Errorf("parsing passwd file: %w", err) + } + + groupMap, err := parsePasswdFile(filepath.Join(rootDir, "/etc/group")) + if err != nil { + return nil, nil, fmt.Errorf("parsing group file: %w", err) + } + + return userMap, groupMap, nil +} + +// parsePasswdFile reads a passwd-format file and returns a map of names to IDs. +func parsePasswdFile(path string) (map[string]string, error) { + idMap := make(map[string]string) + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading file %s: %w", path, err) + } + + for _, line := range strings.Split(string(data), "\n") { + if line == "" { + continue + } + + fields := strings.Split(line, ":") + if len(fields) < 3 { + continue + } + + idMap[fields[0]] = fields[2] + } + + return idMap, nil +} + +// isNumeric is a helper function to check if a string is numeric. +func isNumeric(s string) bool { + _, err := strconv.Atoi(s) + return err == nil +} diff --git a/shared/logger.go b/shared/logger.go index ca04b9b5..5bcc96c7 100644 --- a/shared/logger.go +++ b/shared/logger.go @@ -17,6 +17,8 @@ func GetLogger(debug bool) (*logrus.Logger, error) { PadLevelText: true, } + formatter.EnvironmentOverrideColors = true + logger.Formatter = &formatter if debug {