Skip to content

Commit 9a0278c

Browse files
Add image creator
This PR adds a new tool image creator that creates seed images Added a new module for image creator Added a new share testutils module follow-up PRs: 1. validation logic and tests for image creator configs, tests to check the actual contents of the image. e.g. installed packages, grub.cfg files, etc. 2. Add cli argument for release version to support 2.0/3.0/4.0 AZL packages installation, packagesnapshot time 3. Support for fedora 4. doc updates to run imagecreator and test ### **Checklist** - [ ] Tests added/updated - [ ] Documentation updated (if needed) - [ ] Code conforms to style guidelines
1 parent 29effdd commit 9a0278c

39 files changed

+1262
-356
lines changed

docs/imagecustomizer/developer-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ sudo go test -C ./toolkit/tools ./...
4747
2. Download the test RPM files:
4848

4949
```bash
50-
./toolkit/tools/pkg/imagecustomizerlib/testdata/testrpms/download-test-rpms.sh
50+
./toolkit/tools/internal/testutils/testrpms/download-test-utils.sh
5151
```
5252

5353
3. Run the tests:

toolkit/scripts/tools.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ go_tool_list = \
3434
imager \
3535
isomaker \
3636
osmodifier \
37+
imagecreator \
3738

3839
# For each utility "util", create a "out/tools/util" target which references code in "tools/util/"
3940
go_tool_targets = $(foreach target,$(go_tool_list),$(TOOL_BINS_DIR)/$(target))

toolkit/tools/imagecreator/main.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
// Tool to create and install images
5+
6+
package main
7+
8+
import (
9+
"log"
10+
"maps"
11+
"strings"
12+
13+
"github.com/alecthomas/kong"
14+
"github.com/microsoft/azurelinux/toolkit/tools/imagecustomizerapi"
15+
"github.com/microsoft/azurelinux/toolkit/tools/internal/exekong"
16+
"github.com/microsoft/azurelinux/toolkit/tools/internal/logger"
17+
"github.com/microsoft/azurelinux/toolkit/tools/internal/ptrutils"
18+
"github.com/microsoft/azurelinux/toolkit/tools/pkg/imagecreatorlib"
19+
"github.com/microsoft/azurelinux/toolkit/tools/pkg/imagecustomizerlib"
20+
)
21+
22+
type ImageCreatorCmd struct {
23+
BuildDir string `name:"build-dir" help:"Directory to run build out of." required:""`
24+
ConfigFile string `name:"config-file" help:"Path of the image creator config file." required:""`
25+
RpmSources []string `name:"rpm-source" help:"Path to a RPM repo config file or a directory containing RPMs." required:""`
26+
ToolsTar string `name:"tools-file" help:"Path to tdnf worker tarball" required:""`
27+
OutputImageFile string `name:"output-image-file" help:"Path to write the customized image to."`
28+
OutputImageFormat string `name:"output-image-format" placeholder:"(vhd|vhd-fixed|vhdx|qcow2|raw)" help:"Format of output image." enum:"${imageformat}" default:""`
29+
exekong.LogFlags
30+
}
31+
32+
func main() {
33+
cli := &ImageCreatorCmd{}
34+
35+
vars := kong.Vars{
36+
"imageformat": strings.Join(imagecustomizerapi.SupportedImageFormatTypesImageCreator(), ",") + ",",
37+
"version": imagecustomizerlib.ToolVersion,
38+
}
39+
maps.Copy(vars, exekong.KongVars)
40+
41+
_ = kong.Parse(cli,
42+
vars,
43+
kong.HelpOptions{
44+
Compact: true,
45+
FlagsLast: true,
46+
},
47+
kong.UsageOnError())
48+
49+
logger.InitBestEffort(ptrutils.PtrTo(cli.LogFlags.AsLoggerFlags()))
50+
51+
err := imagecreatorlib.CreateImageWithConfigFile(cli.BuildDir, cli.ConfigFile, cli.RpmSources, cli.ToolsTar, cli.OutputImageFile, cli.OutputImageFormat)
52+
if err != nil {
53+
log.Fatalf("image creation failed:\n%v", err)
54+
}
55+
}

toolkit/tools/imagecustomizerapi/imageFormatType.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ var supportedImageFormatTypes = []string{
3737
string(ImageFormatTypeCosi),
3838
}
3939

40+
var supportedImageFormatTypesImageCreator = []string{
41+
string(ImageFormatTypeVhd),
42+
string(ImageFormatVhdTypeFixed),
43+
string(ImageFormatTypeVhdx),
44+
string(ImageFormatTypeQcow2),
45+
string(ImageFormatTypeRaw),
46+
}
47+
4048
func (ft ImageFormatType) IsValid() error {
4149
if ft != ImageFormatTypeNone && !slices.Contains(SupportedImageFormatTypes(), string(ft)) {
4250
return fmt.Errorf("invalid image format type (%s)", ft)
@@ -49,3 +57,7 @@ func (ft ImageFormatType) IsValid() error {
4957
func SupportedImageFormatTypes() []string {
5058
return supportedImageFormatTypes
5159
}
60+
61+
func SupportedImageFormatTypesImageCreator() []string {
62+
return supportedImageFormatTypesImageCreator
63+
}

toolkit/tools/pkg/imagecustomizerlib/createuuid.go renamed to toolkit/tools/internal/randomization/createuuid.go

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
package imagecustomizerlib
4+
package randomization
55

66
import (
77
"crypto/rand"
88
"encoding/hex"
99
"fmt"
10-
"path/filepath"
1110
"strings"
1211

13-
"github.com/microsoft/azurelinux/toolkit/tools/internal/file"
1412
"github.com/microsoft/azurelinux/toolkit/tools/internal/logger"
15-
"github.com/microsoft/azurelinux/toolkit/tools/internal/sliceutils"
1613
)
1714

1815
const (
19-
UuidSize uint32 = 16
20-
ImageCustomizerReleasePath = "etc/image-customizer-release"
16+
UuidSize uint32 = 16
2117
)
2218

2319
// Create the uuid and return byte array and string representation
24-
func createUuid() ([UuidSize]byte, string, error) {
20+
func CreateUuid() ([UuidSize]byte, string, error) {
2521
uuid, err := generateRandom128BitNumber()
2622
if err != nil {
2723
return uuid, "", err
@@ -54,34 +50,7 @@ func convertUuidToString(uuid [UuidSize]byte) string {
5450
return uuidStr
5551
}
5652

57-
func extractImageUUID(imageConnection *ImageConnection) ([UuidSize]byte, string, error) {
58-
var emptyUuid [UuidSize]byte
59-
60-
releasePath := filepath.Join(imageConnection.Chroot().RootDir(), ImageCustomizerReleasePath)
61-
data, err := file.Read(releasePath)
62-
if err != nil {
63-
return emptyUuid, "", fmt.Errorf("failed to read %s:\n%w", releasePath, err)
64-
}
65-
66-
lines := strings.Split(string(data), "\n")
67-
line, found := sliceutils.FindValueFunc(lines, func(line string) bool {
68-
return strings.HasPrefix(line, "IMAGE_UUID=")
69-
})
70-
if !found {
71-
return emptyUuid, "", fmt.Errorf("IMAGE_UUID not found in %s", releasePath)
72-
}
73-
74-
uuidStr := strings.Trim(strings.TrimPrefix(line, "IMAGE_UUID="), `"`)
75-
76-
parsed, err := parseUuidString(uuidStr)
77-
if err != nil {
78-
return emptyUuid, "", fmt.Errorf("failed to parse IMAGE_UUID (%s):\n%w", uuidStr, err)
79-
}
80-
81-
return parsed, uuidStr, nil
82-
}
83-
84-
func parseUuidString(s string) ([UuidSize]byte, error) {
53+
func ParseUuidString(s string) ([UuidSize]byte, error) {
8554
var uuid [UuidSize]byte
8655

8756
parts := strings.Split(s, "-")

toolkit/tools/internal/safechroot/safechroot.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,6 @@ func (c *Chroot) unmountAndRemove(leaveOnDisk, lazyUnmount bool) (err error) {
670670
umountErr := unix.Unmount(fullPath, unmountFlags)
671671
return umountErr
672672
}, totalAttempts, retryDuration, 2.0)
673-
674673
if err != nil {
675674
err = fmt.Errorf("failed to unmount (%s):\n%w", fullPath, err)
676675
return
@@ -687,23 +686,23 @@ func (c *Chroot) unmountAndRemove(leaveOnDisk, lazyUnmount bool) (err error) {
687686
// defaultMountPoints returns a new copy of the default mount points used by a functional chroot
688687
func defaultMountPoints() []*MountPoint {
689688
return []*MountPoint{
690-
&MountPoint{
689+
{
691690
target: "/dev",
692691
fstype: "devtmpfs",
693692
},
694-
&MountPoint{
693+
{
695694
target: "/proc",
696695
fstype: "proc",
697696
},
698-
&MountPoint{
697+
{
699698
target: "/sys",
700699
fstype: "sysfs",
701700
},
702-
&MountPoint{
701+
{
703702
target: "/run",
704703
fstype: "tmpfs",
705704
},
706-
&MountPoint{
705+
{
707706
target: "/dev/pts",
708707
fstype: "devpts",
709708
data: "gid=5,mode=620",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
CONTAINER_IMAGE="$1"
6+
TOOLS_FILE="$2"
7+
8+
if [[ -z "$CONTAINER_IMAGE" || -z "$TOOLS_FILE" ]]; then
9+
echo "Usage: $0 <container_image> <tools_file>"
10+
exit 1
11+
fi
12+
13+
docker create --name temp-container "$CONTAINER_IMAGE"
14+
docker export temp-container | gzip > "$TOOLS_FILE"
15+
docker rm temp-container

toolkit/tools/pkg/imagecustomizerlib/testdata/testrpms/download-test-rpms.sh renamed to toolkit/tools/internal/testutils/testrpms/download-test-rpms.sh

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,21 @@
1-
#!/usr/bin/env bash
2-
set -eu
1+
#!/bin/bash
32

4-
SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
5-
CONTAINER_TAG="imagecustomizertestrpms:latest"
6-
DOCKERFILE_DIR="$SCRIPT_DIR/downloader"
7-
8-
AZURELINUX_2_CONTAINER_IMAGE="mcr.microsoft.com/cbl-mariner/base/core:2.0"
9-
10-
IMAGE_VERSION="2.0"
3+
set -e
4+
CONTAINER_IMAGE="$1"
5+
IMAGE_VERSION="$2"
6+
IMAGE_CREATOR="$3"
117

12-
while getopts "t:" flag
13-
do
14-
case "${flag}" in
15-
t) IMAGE_VERSION="$OPTARG";;
16-
h) ;;&
17-
?)
18-
echo "Usage: download-test-rpms.sh [-t IMAGE_VERSION]"
19-
echo ""
20-
echo "Args:"
21-
echo " -t IMAGE_VERSION The Azure Image version to download the RPMs for."
22-
echo " -h Show help"
23-
exit 1;;
24-
esac
25-
done
26-
27-
case "${IMAGE_VERSION}" in
28-
2.0)
29-
CONTAINER_IMAGE="$AZURELINUX_2_CONTAINER_IMAGE"
30-
;;
31-
*)
32-
echo "error: unsupported Azure Linux version: $IMAGE_VERSION"
33-
exit 1;;
34-
esac
35-
36-
set -x
8+
# Check if the required arguments are provided
9+
if [[ -z "$CONTAINER_IMAGE" || -z "$IMAGE_VERSION" || -z "$IMAGE_CREATOR" ]]; then
10+
echo "Usage: $0 <container_image> <image_version> <image_creator>"
11+
echo "Example: $0 mcr.microsoft.com/azurelinux/base/core:3.0 3.0 true"
12+
exit 1
13+
fi
3714

15+
SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
16+
DOCKERFILE_DIR="$SCRIPT_DIR/downloader"
3817
DOWNLOADER_RPMS_DIRS="$SCRIPT_DIR/downloadedrpms"
18+
CONTAINER_TAG="imagecustomizertestrpms:latest"
3919
OUT_DIR="$DOWNLOADER_RPMS_DIRS/$IMAGE_VERSION"
4020
REPO_WITH_KEY_FILE="$DOWNLOADER_RPMS_DIRS/rpms-$IMAGE_VERSION-withkey.repo"
4121
REPO_NO_KEY_FILE="$DOWNLOADER_RPMS_DIRS/rpms-$IMAGE_VERSION-nokey.repo"
@@ -44,7 +24,8 @@ mkdir -p "$OUT_DIR"
4424

4525
# Build a container image that contains the RPMs.
4626
docker build \
47-
--build-arg "baseimage=$AZURELINUX_2_CONTAINER_IMAGE" \
27+
--build-arg "baseimage=$CONTAINER_IMAGE" \
28+
--build-arg "imagecreator=$IMAGE_CREATOR" \
4829
--tag "$CONTAINER_TAG" \
4930
"$DOCKERFILE_DIR"
5031

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
4+
SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
5+
6+
AZURELINUX_2_CONTAINER_IMAGE="mcr.microsoft.com/cbl-mariner/base/core:2.0"
7+
AZURELINUX_3_CONTAINER_IMAGE="mcr.microsoft.com/azurelinux/base/core:3.0"
8+
9+
IMAGE_VERSION="2.0"
10+
11+
IMAGE_CREATOR="false"
12+
TOOLS_FILE="$SCRIPT_DIR/tools.tar.gz"
13+
14+
15+
while getopts "s:t:" flag
16+
do
17+
case "${flag}" in
18+
s) IMAGE_CREATOR="$OPTARG";;
19+
t) IMAGE_VERSION="$OPTARG";;
20+
h) ;;&
21+
?)
22+
echo "Usage: download-test-utils.sh [-t IMAGE_VERSION] [-s IMAGE_CREATOR]"
23+
echo ""
24+
echo "Args:"
25+
echo " -t IMAGE_VERSION The Azure Image version to download the RPMs for."
26+
echo " -s IMAGE_CREATOR If set to true, the script will create a tar.gz file with the tools and download the rpms needed to test imagecreator."
27+
echo " -h Show help"
28+
exit 1;;
29+
esac
30+
done
31+
32+
33+
case "${IMAGE_VERSION}" in
34+
3.0)
35+
CONTAINER_IMAGE="$AZURELINUX_3_CONTAINER_IMAGE"
36+
;;
37+
2.0)
38+
CONTAINER_IMAGE="$AZURELINUX_2_CONTAINER_IMAGE"
39+
;;
40+
*)
41+
echo "error: unsupported Azure Linux version: $IMAGE_VERSION"
42+
exit 1;;
43+
esac
44+
45+
set -x
46+
47+
# call the script to create the tools file if requested
48+
if [ "$IMAGE_CREATOR" = "true" ]; then
49+
echo "Creating tools file: $TOOLS_FILE"
50+
$SCRIPT_DIR/create-tools-file.sh "$CONTAINER_IMAGE" "$TOOLS_FILE"
51+
echo "Tools file created successfully."
52+
else
53+
echo "Skipping tools file creation."
54+
fi
55+
56+
# call the script to download the rpms
57+
echo "Downloading test rpms for Azure Linux version: $IMAGE_VERSION"
58+
$SCRIPT_DIR/download-test-rpms.sh "$CONTAINER_IMAGE" "$IMAGE_VERSION" "$IMAGE_CREATOR"
59+
echo "Test rpms downloaded successfully."
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
ARG baseimage
2+
FROM ${baseimage}
3+
ARG imagecreator
4+
5+
RUN tdnf update -y && \
6+
tdnf install -y dnf dnf-plugins-core createrepo_c
7+
8+
RUN dnf download -y --resolve --alldeps --destdir /downloadedrpms \
9+
jq golang
10+
11+
RUN if [ "${imagecreator}" = "true" ]; then \
12+
13+
dnf download -y --resolve --alldeps --destdir /downloadedrpms \
14+
azurelinux-release \
15+
azurelinux-repos azurelinux-rpm-macros bash dbus dracut-hostonly e2fsprogs filesystem \
16+
grub2 grub2-efi-binary iana-etc iproute iputils irqbalance \
17+
ncurses-libs openssl rpm rpm-libs shadow-utils shim sudo \
18+
systemd systemd-networkd systemd-resolved systemd-udev tdnf \
19+
tdnf-plugin-repogpgcheck util-linux zlib kernel initramfs ; \
20+
21+
fi
22+
23+
# Add repo metadata, so that the directory can be used in a .repo file.
24+
RUN createrepo --compatibility --update /downloadedrpms

0 commit comments

Comments
 (0)