debian-forge-composer/internal/blueprint/fsnode_customizations.go
Tomáš Hozza d4e3173234 Delete unused blueprint.ValidateDirFileCustomizations() function
This function is no longer used by any code. Instead, its copy in the
`osbuild/images` repository is used by distro definitions to validate
the customization.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2023-10-20 18:59:00 +02:00

332 lines
8.6 KiB
Go

package blueprint
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strconv"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/fsnode"
)
// validateModeString checks that the given string is a valid mode octal number
func validateModeString(mode string) error {
// Check that the mode string matches the octal format regular expression.
// The leading is optional.
if regexp.MustCompile(`^[0]{0,1}[0-7]{3}$`).MatchString(mode) {
return nil
}
return fmt.Errorf("invalid mode %s: must be an octal number", mode)
}
// DirectoryCustomization represents a directory to be created in the image
type DirectoryCustomization struct {
// Absolute path to the directory
Path string `json:"path" toml:"path"`
// Owner of the directory specified as a string (user name), int64 (UID) or nil
User interface{} `json:"user,omitempty" toml:"user,omitempty"`
// Owner of the directory specified as a string (group name), int64 (UID) or nil
Group interface{} `json:"group,omitempty" toml:"group,omitempty"`
// Permissions of the directory specified as an octal number
Mode string `json:"mode,omitempty" toml:"mode,omitempty"`
// EnsureParents ensures that all parent directories of the directory exist
EnsureParents bool `json:"ensure_parents,omitempty" toml:"ensure_parents,omitempty"`
}
// Custom TOML unmarshalling for DirectoryCustomization with validation
func (d *DirectoryCustomization) UnmarshalTOML(data interface{}) error {
var dir DirectoryCustomization
dataMap, _ := data.(map[string]interface{})
switch path := dataMap["path"].(type) {
case string:
dir.Path = path
default:
return fmt.Errorf("UnmarshalTOML: path must be a string")
}
switch user := dataMap["user"].(type) {
case string:
dir.User = user
case int64:
dir.User = user
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: user must be a string or an integer, got %T", user)
}
switch group := dataMap["group"].(type) {
case string:
dir.Group = group
case int64:
dir.Group = group
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: group must be a string or an integer")
}
switch mode := dataMap["mode"].(type) {
case string:
dir.Mode = mode
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: mode must be a string")
}
switch ensureParents := dataMap["ensure_parents"].(type) {
case bool:
dir.EnsureParents = ensureParents
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: ensure_parents must be a bool")
}
// try converting to fsnode.Directory to validate all values
_, err := dir.ToFsNodeDirectory()
if err != nil {
return err
}
*d = dir
return nil
}
// Custom JSON unmarshalling for DirectoryCustomization with validation
func (d *DirectoryCustomization) UnmarshalJSON(data []byte) error {
type directoryCustomization DirectoryCustomization
var dirPrivate directoryCustomization
if err := json.Unmarshal(data, &dirPrivate); err != nil {
return err
}
dir := DirectoryCustomization(dirPrivate)
if uid, ok := dir.User.(float64); ok {
// check if uid can be converted to int64
if uid != float64(int64(uid)) {
return fmt.Errorf("invalid user %f: must be an integer", uid)
}
dir.User = int64(uid)
}
if gid, ok := dir.Group.(float64); ok {
// check if gid can be converted to int64
if gid != float64(int64(gid)) {
return fmt.Errorf("invalid group %f: must be an integer", gid)
}
dir.Group = int64(gid)
}
// try converting to fsnode.Directory to validate all values
_, err := dir.ToFsNodeDirectory()
if err != nil {
return err
}
*d = dir
return nil
}
// ToFsNodeDirectory converts the DirectoryCustomization to an fsnode.Directory
func (d DirectoryCustomization) ToFsNodeDirectory() (*fsnode.Directory, error) {
var mode *os.FileMode
if d.Mode != "" {
err := validateModeString(d.Mode)
if err != nil {
return nil, err
}
modeNum, err := strconv.ParseUint(d.Mode, 8, 32)
if err != nil {
return nil, fmt.Errorf("invalid mode %s: %v", d.Mode, err)
}
mode = common.ToPtr(os.FileMode(modeNum))
}
return fsnode.NewDirectory(d.Path, mode, d.User, d.Group, d.EnsureParents)
}
// DirectoryCustomizationsToFsNodeDirectories converts a slice of DirectoryCustomizations
// to a slice of fsnode.Directories
func DirectoryCustomizationsToFsNodeDirectories(dirs []DirectoryCustomization) ([]*fsnode.Directory, error) {
if len(dirs) == 0 {
return nil, nil
}
var fsDirs []*fsnode.Directory
var errors []error
for _, dir := range dirs {
fsDir, err := dir.ToFsNodeDirectory()
if err != nil {
errors = append(errors, err)
}
fsDirs = append(fsDirs, fsDir)
}
if len(errors) > 0 {
return nil, fmt.Errorf("invalid directory customizations: %v", errors)
}
return fsDirs, nil
}
// FileCustomization represents a file to be created in the image
type FileCustomization struct {
// Absolute path to the file
Path string `json:"path" toml:"path"`
// Owner of the directory specified as a string (user name), int64 (UID) or nil
User interface{} `json:"user,omitempty" toml:"user,omitempty"`
// Owner of the directory specified as a string (group name), int64 (UID) or nil
Group interface{} `json:"group,omitempty" toml:"group,omitempty"`
// Permissions of the file specified as an octal number
Mode string `json:"mode,omitempty" toml:"mode,omitempty"`
// Data is the file content in plain text
Data string `json:"data,omitempty" toml:"data,omitempty"`
}
// Custom TOML unmarshalling for FileCustomization with validation
func (f *FileCustomization) UnmarshalTOML(data interface{}) error {
var file FileCustomization
dataMap, _ := data.(map[string]interface{})
switch path := dataMap["path"].(type) {
case string:
file.Path = path
default:
return fmt.Errorf("UnmarshalTOML: path must be a string")
}
switch user := dataMap["user"].(type) {
case string:
file.User = user
case int64:
file.User = user
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: user must be a string or an integer")
}
switch group := dataMap["group"].(type) {
case string:
file.Group = group
case int64:
file.Group = group
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: group must be a string or an integer")
}
switch mode := dataMap["mode"].(type) {
case string:
file.Mode = mode
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: mode must be a string")
}
switch data := dataMap["data"].(type) {
case string:
file.Data = data
case nil:
break
default:
return fmt.Errorf("UnmarshalTOML: data must be a string")
}
// try converting to fsnode.File to validate all values
_, err := file.ToFsNodeFile()
if err != nil {
return err
}
*f = file
return nil
}
// Custom JSON unmarshalling for FileCustomization with validation
func (f *FileCustomization) UnmarshalJSON(data []byte) error {
type fileCustomization FileCustomization
var filePrivate fileCustomization
if err := json.Unmarshal(data, &filePrivate); err != nil {
return err
}
file := FileCustomization(filePrivate)
if uid, ok := file.User.(float64); ok {
// check if uid can be converted to int64
if uid != float64(int64(uid)) {
return fmt.Errorf("invalid user %f: must be an integer", uid)
}
file.User = int64(uid)
}
if gid, ok := file.Group.(float64); ok {
// check if gid can be converted to int64
if gid != float64(int64(gid)) {
return fmt.Errorf("invalid group %f: must be an integer", gid)
}
file.Group = int64(gid)
}
// try converting to fsnode.File to validate all values
_, err := file.ToFsNodeFile()
if err != nil {
return err
}
*f = file
return nil
}
// ToFsNodeFile converts the FileCustomization to an fsnode.File
func (f FileCustomization) ToFsNodeFile() (*fsnode.File, error) {
var data []byte
if f.Data != "" {
data = []byte(f.Data)
}
var mode *os.FileMode
if f.Mode != "" {
err := validateModeString(f.Mode)
if err != nil {
return nil, err
}
modeNum, err := strconv.ParseUint(f.Mode, 8, 32)
if err != nil {
return nil, fmt.Errorf("invalid mode %s: %v", f.Mode, err)
}
mode = common.ToPtr(os.FileMode(modeNum))
}
return fsnode.NewFile(f.Path, mode, f.User, f.Group, data)
}
// FileCustomizationsToFsNodeFiles converts a slice of FileCustomization to a slice of *fsnode.File
func FileCustomizationsToFsNodeFiles(files []FileCustomization) ([]*fsnode.File, error) {
if len(files) == 0 {
return nil, nil
}
var fsFiles []*fsnode.File
var errors []error
for _, file := range files {
fsFile, err := file.ToFsNodeFile()
if err != nil {
errors = append(errors, err)
}
fsFiles = append(fsFiles, fsFile)
}
if len(errors) > 0 {
return nil, fmt.Errorf("invalid file customizations: %v", errors)
}
return fsFiles, nil
}