Skip to content

Commit

Permalink
Image history (#42)
Browse files Browse the repository at this point in the history
This PR introduces a feature to add image customization history to an
image created by the image customizer tool.
The feature aims to record the image customization history in a
structured, parseable format.
A config option is added to disable the feature.

Tests will be added in a future PR.

---

### **Checklist**
- [ ] Tests added/updated
- [x] Documentation updated (if needed)
- [x] Code conforms to style guidelines
  • Loading branch information
amritakohli authored Dec 19, 2024
1 parent 86a51b9 commit 63c4822
Show file tree
Hide file tree
Showing 31 changed files with 490 additions and 117 deletions.
29 changes: 16 additions & 13 deletions docs/imagecustomizer/api/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,42 +43,44 @@ The top level type for the YAML file is the [config](./configuration/config.md)

10. Write the `/etc/image-customizer-release` file.

11. If the bootloader [resetType](./configuration/bootloader.md#resettype-string) is set
11. Write the image history file.

12. If the bootloader [resetType](./configuration/bootloader.md#resettype-string) is set
to `hard-reset`, then reset the boot-loader.

If the bootloader [resetType](./configuration/bootloader.md#resettype-string) is not
set, then append the
[extraCommandLine](./configuration/kernelcommandline.md#extracommandline-string)
value to the existing `grub.cfg` file.

12. Update the SELinux mode. [mode](./configuration/selinux.md#mode-string)
13. Update the SELinux mode. [mode](./configuration/selinux.md#mode-string)

13. If ([overlays](./configuration/os.md#overlays-overlay)) are specified, then add the
14. If ([overlays](./configuration/os.md#overlays-overlay)) are specified, then add the
overlay driver and update the fstab file with the overlay mount information.

14. If a ([verity](./configuration/storage.md#verity-verity)) device is specified, then
15. If a ([verity](./configuration/storage.md#verity-verity)) device is specified, then
add the dm-verity dracut driver and update the grub config.

15. Regenerate the initramfs file (if needed).
16. Regenerate the initramfs file (if needed).

16. Run ([postCustomization](./configuration/scripts.md#postcustomization-script)) scripts.
17. Run ([postCustomization](./configuration/scripts.md#postcustomization-script)) scripts.

17. Restore the `/etc/resolv.conf` file.
18. Restore the `/etc/resolv.conf` file.

18. If SELinux is enabled, call `setfiles`.
19. If SELinux is enabled, call `setfiles`.

19. Run finalize image scripts. ([finalizeCustomization](./configuration/scripts.md#finalizecustomization-script))
20. Run finalize image scripts. ([finalizeCustomization](./configuration/scripts.md#finalizecustomization-script))

20. If [--shrink-filesystems](./cli.md#shrink-filesystems) is specified, then shrink
21. If [--shrink-filesystems](./cli.md#shrink-filesystems) is specified, then shrink
the file systems.

21. If a ([verity](./configuration/storage.md#verity-verity)) device is specified, then
22. If a ([verity](./configuration/storage.md#verity-verity)) device is specified, then
create the hash tree and update the grub config.

22. If the output format is set to `iso`, copy additional iso media files.
23. If the output format is set to `iso`, copy additional iso media files.
([iso](./configuration/iso.md))

23. If [--output-pxe-artifacts-dir](./cli.md#output-pxe-artifacts-dir) is specified,
24. If [--output-pxe-artifacts-dir](./cli.md#output-pxe-artifacts-dir) is specified,
then export the ISO image contents to the specified folder.

## /etc/resolv.conf
Expand Down Expand Up @@ -206,6 +208,7 @@ os:
- [overlays](./configuration/os.md#overlays-overlay) ([overlay type](./configuration/overlay.md))
- [uki](./configuration/os.md#uki-uki) ([uki type](./configuration/uki.md))
- [kernels](./configuration/uki.md#kernels)
- [imageHistory](./configuration/imagehistory.md)
- [scripts](./configuration/config.md#scripts-scripts) ([scripts type](./configuration/scripts.md))
- [postCustomization](./configuration/scripts.md#postcustomization-script) ([script type](./configuration/script.md))
- [path](./configuration/script.md#script-path)
Expand Down
11 changes: 11 additions & 0 deletions docs/imagecustomizer/api/configuration/os.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,14 @@ os:
enable:
- sshd
```
## imageHistory [string]
Options for configuring image history.
Set value to `none` to disable.

```yaml
os:
imageHistory: none
```
10 changes: 6 additions & 4 deletions toolkit/tools/imagecustomizerapi/additionalfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ type AdditionalFileList []AdditionalFile

type AdditionalFile struct {
// The destination file path in the target OS that the file will be copied to.
Destination string `yaml:"destination"`
Destination string `yaml:"destination" json:"destination,omitempty"`

// The source file path of the file that will copied.
// Mutally exclusive with 'contents'.
Source string `yaml:"source"`
Source string `yaml:"source" json:"source,omitempty"`

// A string that will be used as the contents of the file.
// Mutally exclusive with 'source'.
Content *string `yaml:"content"`
Content *string `yaml:"content" json:"content,omitempty"`

// The file permissions to set on the file.
Permissions *FilePermissions `yaml:"permissions"`
Permissions *FilePermissions `yaml:"permissions" json:"permissions,omitempty"`

SHA256Hash string `json:"sha256hash,omitempty"`
}

func (l AdditionalFileList) IsValid() (err error) {
Expand Down
2 changes: 1 addition & 1 deletion toolkit/tools/imagecustomizerapi/bootloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

type BootLoader struct {
ResetType ResetBootLoaderType `yaml:"resetType"`
ResetType ResetBootLoaderType `yaml:"resetType" json:"resetType,omitempty"`
}

func (b *BootLoader) IsValid() error {
Expand Down
12 changes: 6 additions & 6 deletions toolkit/tools/imagecustomizerapi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import (
)

type Config struct {
Storage Storage `yaml:"storage"`
Iso *Iso `yaml:"iso"`
Pxe *Pxe `yaml:"pxe"`
OS *OS `yaml:"os"`
Scripts Scripts `yaml:"scripts"`
PreviewFeatures []string `yaml:"previewFeatures"`
Storage Storage `yaml:"storage" json:"storage,omitempty"`
Iso *Iso `yaml:"iso" json:"iso,omitempty"`
Pxe *Pxe `yaml:"pxe" json:"pxe,omitempty"`
OS *OS `yaml:"os" json:"os,omitempty"`
Scripts Scripts `yaml:"scripts" json:"scripts,omitempty"`
PreviewFeatures []string `yaml:"previewFeatures" json:"previewFeatures,omitempty"`
}

func (c *Config) IsValid() (err error) {
Expand Down
12 changes: 7 additions & 5 deletions toolkit/tools/imagecustomizerapi/dirconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,24 @@ type DirConfigList []DirConfig

type DirConfig struct {
// The path to the source directory that will be copied (can be relative or absolute path).
Source string `yaml:"source"`
Source string `yaml:"source" json:"source,omitempty"`

// The absolute path in the target OS that the directory will be copied to.
Destination string `yaml:"destination"`
Destination string `yaml:"destination" json:"destination,omitempty"`

// The permissions to set on all of the new directories being created on the target OS (including the top-level directory).
// Note: If this value is not specified in the config, the permissions for these directories will be set to 0755.
NewDirPermissions *FilePermissions `yaml:"newDirPermissions"`
NewDirPermissions *FilePermissions `yaml:"newDirPermissions" json:"newDirPermissions,omitempty"`

// The permissions to set on the directories being copied that already do exist on the target OS (including the top-level directory).
// Note: If this value is not specified in the config, the permissions for this field will be the same as that of the pre-existing directory.
MergedDirPermissions *FilePermissions `yaml:"mergedDirPermissions"`
MergedDirPermissions *FilePermissions `yaml:"mergedDirPermissions" json:"mergedDirPermissions,omitempty"`

// The permissions to set on the children file of the directory.
// Note: If this value is not specified in the config, the permissions for these directories will be set to 0755.
ChildFilePermissions *FilePermissions `yaml:"childFilePermissions"`
ChildFilePermissions *FilePermissions `yaml:"childFilePermissions" json:"childFilePermissions,omitempty"`

SHA256HashMap map[string]string `json:"sha256hashmap,omitempty"`
}

func (l *DirConfigList) IsValid() (err error) {
Expand Down
6 changes: 3 additions & 3 deletions toolkit/tools/imagecustomizerapi/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ const (

type Disk struct {
// The type of partition table to use (e.g. mbr, gpt)
PartitionTableType PartitionTableType `yaml:"partitionTableType"`
PartitionTableType PartitionTableType `yaml:"partitionTableType" json:"partitionTableType,omitempty"`

// The virtual size of the disk.
// Note: This value is filled in by IsValid().
MaxSize *DiskSize `yaml:"maxSize"`
MaxSize *DiskSize `yaml:"maxSize" json:"maxSize,omitempty"`

// The partitions to allocate on the disk.
Partitions []Partition `yaml:"partitions"`
Partitions []Partition `yaml:"partitions" json:"partitions,omitempty"`
}

func (d *Disk) IsValid() error {
Expand Down
38 changes: 38 additions & 0 deletions toolkit/tools/imagecustomizerapi/disksize.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package imagecustomizerapi

import (
"encoding/json"
"fmt"
"regexp"
"strconv"
Expand Down Expand Up @@ -31,6 +32,26 @@ func (s *DiskSize) UnmarshalYAML(value *yaml.Node) error {
return fmt.Errorf("failed to parse disk size:\n%w", err)
}

return parseAndSetDiskSize(stringValue, s)
}

func (s DiskSize) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}

func (s *DiskSize) UnmarshalJSON(data []byte) error {
var err error

var stringValue string
err = json.Unmarshal(data, &stringValue)
if err != nil {
return fmt.Errorf("failed to parse disk size:\n%w", err)
}

return parseAndSetDiskSize(stringValue, s)
}

func parseAndSetDiskSize(stringValue string, s *DiskSize) error {
diskSize, err := parseDiskSize(stringValue)
if err != nil {
return fmt.Errorf("%w:\nexpected format: <NUM>(K|M|G|T) (e.g. 100M, 1G)", err)
Expand Down Expand Up @@ -98,3 +119,20 @@ func parseDiskSize(diskSizeString string) (DiskSize, error) {

return DiskSize(num), nil
}

// String returns the string representation of DiskSize in the most appropriate unit
// such that it matches the input format.
func (s DiskSize) String() string {
switch {
case s%diskutils.TiB == 0:
return fmt.Sprintf("%dT", s/diskutils.TiB)
case s%diskutils.GiB == 0:
return fmt.Sprintf("%dG", s/diskutils.GiB)
case s%diskutils.MiB == 0:
return fmt.Sprintf("%dM", s/diskutils.MiB)
case s%diskutils.KiB == 0:
return fmt.Sprintf("%dK", s/diskutils.KiB)
default:
return fmt.Sprintf("%d", s)
}
}
8 changes: 4 additions & 4 deletions toolkit/tools/imagecustomizerapi/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import (
// FileSystem holds the file system information for a partition.
type FileSystem struct {
// DeviceId is the ID of the source partition.
DeviceId string `yaml:"deviceId"`
DeviceId string `yaml:"deviceId" json:"deviceId,omitempty"`
// FileSystemType is the type of file system to use on the partition.
Type FileSystemType `yaml:"type"`
Type FileSystemType `yaml:"type" json:"type,omitempty"`
// MountPoint contains the mount settings.
MountPoint *MountPoint `yaml:"mountPoint"`
MountPoint *MountPoint `yaml:"mountPoint" json:"mountPoint,omitempty"`

// If 'DeviceId' points at a verity device, this value is the 'Id' of the data partition.
// Otherwise, it is the same as 'DeviceId'.
// Value is filled in by Storage.IsValid().
PartitionId string
PartitionId string `json:"-"`
}

// IsValid returns an error if the MountPoint is not valid
Expand Down
26 changes: 26 additions & 0 deletions toolkit/tools/imagecustomizerapi/imagehistory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package imagecustomizerapi

import (
"fmt"
)

type ImageHistory string

const (
ImageHistoryDefault ImageHistory = ""
ImageHistoryNone ImageHistory = "none"
)

func (t ImageHistory) IsValid() error {
switch t {
case ImageHistoryDefault, ImageHistoryNone:
// All good.
return nil

default:
return fmt.Errorf("invalid imageHistory value (%s)", t)
}
}
4 changes: 2 additions & 2 deletions toolkit/tools/imagecustomizerapi/iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

// Iso defines how the generated iso media should be configured.
type Iso struct {
KernelCommandLine KernelCommandLine `yaml:"kernelCommandLine"`
AdditionalFiles AdditionalFileList `yaml:"additionalFiles"`
KernelCommandLine KernelCommandLine `yaml:"kernelCommandLine" json:"kernelCommandLine,omitempty"`
AdditionalFiles AdditionalFileList `yaml:"additionalFiles" json:"additionalFiles,omitempty"`
}

func (i *Iso) IsValid() error {
Expand Down
2 changes: 1 addition & 1 deletion toolkit/tools/imagecustomizerapi/kernelcommandline.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

type KernelCommandLine struct {
// Extra kernel command line args.
ExtraCommandLine []string `yaml:"extraCommandLine"`
ExtraCommandLine []string `yaml:"extraCommandLine" json:"extraCommandLine,omitempty"`
}

func (k *KernelCommandLine) IsValid() error {
Expand Down
6 changes: 3 additions & 3 deletions toolkit/tools/imagecustomizerapi/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
)

type Module struct {
Name string `yaml:"name"`
LoadMode ModuleLoadMode `yaml:"loadMode"`
Options map[string]string `yaml:"options"`
Name string `yaml:"name" json:"name,omitempty"`
LoadMode ModuleLoadMode `yaml:"loadMode" json:"loadMode,omitempty"`
Options map[string]string `yaml:"options" json:"options,omitempty"`
}

type ModuleList []Module
Expand Down
6 changes: 3 additions & 3 deletions toolkit/tools/imagecustomizerapi/mountpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (
// MountPoint holds the mounting information for each partition.
type MountPoint struct {
// The ID type to use for the source in the /etc/fstab file.
IdType MountIdentifierType `yaml:"idType"`
IdType MountIdentifierType `yaml:"idType" json:"idType,omitempty"`
// The additional options for the mount.
Options string `yaml:"options"`
Options string `yaml:"options" json:"options,omitempty"`
// The target directory path of the mount.
Path string `yaml:"path"`
Path string `yaml:"path" json:"path,omitempty"`
}

// UnmarshalYAML enables MountPoint to handle both a shorthand path and a structured object.
Expand Down
30 changes: 18 additions & 12 deletions toolkit/tools/imagecustomizerapi/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ import (

// OS defines how each system present on the image is supposed to be configured.
type OS struct {
Hostname string `yaml:"hostname"`
Packages Packages `yaml:"packages"`
SELinux SELinux `yaml:"selinux"`
KernelCommandLine KernelCommandLine `yaml:"kernelCommandLine"`
AdditionalFiles AdditionalFileList `yaml:"additionalFiles"`
AdditionalDirs DirConfigList `yaml:"additionalDirs"`
Users []User `yaml:"users"`
Services Services `yaml:"services"`
Modules ModuleList `yaml:"modules"`
Overlays *[]Overlay `yaml:"overlays"`
BootLoader BootLoader `yaml:"bootloader"`
Uki *Uki `yaml:"uki"`
Hostname string `yaml:"hostname" json:"hostname,omitempty"`
Packages Packages `yaml:"packages" json:"packages,omitempty"`
SELinux SELinux `yaml:"selinux" json:"selinux,omitempty"`
KernelCommandLine KernelCommandLine `yaml:"kernelCommandLine" json:"kernelCommandLine,omitempty"`
AdditionalFiles AdditionalFileList `yaml:"additionalFiles" json:"additionalFiles,omitempty"`
AdditionalDirs DirConfigList `yaml:"additionalDirs" json:"additionalDirs,omitempty"`
Users []User `yaml:"users" json:"users,omitempty"`
Services Services `yaml:"services" json:"services,omitempty"`
Modules ModuleList `yaml:"modules" json:"modules,omitempty"`
Overlays *[]Overlay `yaml:"overlays" json:"overlays,omitempty"`
BootLoader BootLoader `yaml:"bootloader" json:"bootloader,omitempty"`
Uki *Uki `yaml:"uki" json:"uki,omitempty"`
ImageHistory ImageHistory `yaml:"imageHistory" json:"imageHistory,omitempty"`
}

func (s *OS) IsValid() error {
Expand All @@ -39,6 +40,11 @@ func (s *OS) IsValid() error {
}
}

err = s.ImageHistory.IsValid()
if err != nil {
return fmt.Errorf("invalid imageHistory:\n%w", err)
}

err = s.SELinux.IsValid()
if err != nil {
return fmt.Errorf("invalid selinux:\n%w", err)
Expand Down
14 changes: 7 additions & 7 deletions toolkit/tools/imagecustomizerapi/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
)

type Overlay struct {
LowerDirs []string `yaml:"lowerDirs"`
UpperDir string `yaml:"upperDir"`
WorkDir string `yaml:"workDir"`
MountPoint string `yaml:"mountPoint"`
IsInitrdOverlay bool `yaml:"isInitrdOverlay"`
MountDependencies []string `yaml:"mountDependencies"`
MountOptions string `yaml:"mountOptions"`
LowerDirs []string `yaml:"lowerDirs" json:"lowerDirs,omitempty"`
UpperDir string `yaml:"upperDir" json:"upperDir,omitempty"`
WorkDir string `yaml:"workDir" json:"workDir,omitempty"`
MountPoint string `yaml:"mountPoint" json:"mountPoint,omitempty"`
IsInitrdOverlay bool `yaml:"isInitrdOverlay" json:"isInitrdOverlay,omitempty"`
MountDependencies []string `yaml:"mountDependencies" json:"mountDependencies,omitempty"`
MountOptions string `yaml:"mountOptions" json:"mountOptions,omitempty"`
}

func (o *Overlay) IsValid() error {
Expand Down
Loading

0 comments on commit 63c4822

Please sign in to comment.