Skip to content

Commit 2586e97

Browse files
George Milekagmileka
andauthored
feat: add e2e test for image customization (#355)
* feat: added image customizer scenario test * Addressed copilot feedback * Address Reuben's feedback --------- Co-authored-by: George Mileka <gmileka@outlook.com>
1 parent 7ded7da commit 2586e97

11 files changed

Lines changed: 372 additions & 31 deletions

File tree

internal/app/azldev/cmds/image/customize.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ func NewImageCustomizeCmd() *cobra.Command {
2727
}
2828

2929
cmd.Flags().StringVarP(&options.imageFile, "image-file", "", "",
30-
"Path of the base Azure Linux image which the customization will be applied to")
30+
"Path of the base Azure Linux image to be customized. Cannot be specified with --image-tag")
31+
cmd.Flags().StringVarP(&options.imageTag, "image-tag", "", "",
32+
"Container tag for the MCR base Azure Linux image to be downloaded and customized."+
33+
" Cannot be specified with --image-file")
3134
cmd.Flags().StringVarP(&options.configFile, "config-file", "", "",
3235
"Path of the image customization config file")
3336
cmd.Flags().StringVar(&options.outputImageFormat, "output-image-format", "",
3437
"Format of output image ("+getImageCustomizerImageFormatsString()+")")
3538
cmd.Flags().StringVar(&options.outputPath, "output-path", "",
36-
"Path to write the customized image artifacts to. It can be a path to "+
37-
"a file if the output format is a single file format (e.g., vhd, qcow2), "+
38-
"or a path to a directory if the output format is a multi-file format (e.g., pxe-dir, pxe-tar)")
39+
"Path to write the customized image artifacts to. It can be a path to"+
40+
" a file if the output format is a single file format (e.g., vhd, qcow2),"+
41+
" or a path to a directory if the output format is a multi-file format (e.g., pxe-dir, pxe-tar)")
3942
cmd.Flags().StringSliceVar(&options.rpmSources, "rpm-source", []string{},
4043
"Path to a RPM repo config file or a directory containing RPMs")
4144
cmd.Flags().BoolVar(&options.disableBaseImageRpmRepos, "disable-base-image-rpm-repos", false,
@@ -53,15 +56,17 @@ func NewImageCustomizeCmd() *cobra.Command {
5356
// Customizer, we are requiring them in azldev so that we can deduce which
5457
// folders to mount into the container.
5558
// See: https://dev.azure.com/mariner-org/polar/_workitems/edit/15282
56-
_ = cmd.MarkFlagRequired("image-file")
5759
_ = cmd.MarkFlagRequired("config-file")
5860
_ = cmd.MarkFlagRequired("output-path")
5961

62+
cmd.MarkFlagsMutuallyExclusive("image-file", "image-tag")
63+
cmd.MarkFlagsOneRequired("image-file", "image-tag")
64+
6065
return cmd
6166
}
6267

6368
func customizeImage(
6469
env *azldev.Env, options *imageCustomizerOptions,
6570
) error {
66-
return runImageCustomizerContainer(env, imageCustomizerCustomize, options)
71+
return runImageCustomizerContainer(env, imageCustomizerCustomizeCmd, options)
6772
}

internal/app/azldev/cmds/image/imageutils.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ import (
1818
)
1919

2020
const (
21-
logFileTimeFormat = "20060102-150405"
22-
imageCustomizerCustomize = "customize"
23-
imageCustomizerInject = "inject-files"
21+
logFileTimeFormat = "20060102-150405"
22+
imageCustomizerCustomizeCmd = "customize"
23+
imageCustomizerInjectCmd = "inject-files"
2424
// The Image Customizer log always goes to a debug-level log file - regardless
2525
// of the log level set for azldev.
2626
// The log level set for azldev controls what is shown on the console.
2727
defaultLogLevel = "debug"
28+
29+
entryPointSwitch = "--entrypoint"
30+
downloadAndCustomizeScriptPath = "/usr/local/bin/run.sh"
2831
)
2932

3033
type imageCustomizerOptions struct {
3134
imageFile string
35+
imageTag string
3236
configFile string
3337
outputImageFormat string
3438
outputPath string
@@ -52,15 +56,17 @@ func getImageCustomizerImageFormatsString() string {
5256
}
5357

5458
func buildDockerArgs(
55-
buildDir, configDir, inputImageDir, logsDir, outputPathDir string, rpmSources []rpmSourceInfo,
59+
options *imageCustomizerOptions, buildDir string, logsDir string, rpmSources []rpmSourceInfo,
5660
) []string {
61+
configDir := path.Dir(options.configFile)
62+
outputPathDir := path.Dir(options.outputPath)
63+
5764
args := []string{
5865
"run", "--rm",
5966
docker.InteractiveFlag, // Ensure we get a TTY (not the default when run with --privileged)
6067
docker.PrivilegedFlag,
6168
"-v", buildDir + ":" + buildDir + docker.MountRWOption,
6269
"-v", configDir + ":" + configDir + docker.MountROOption,
63-
"-v", inputImageDir + ":" + inputImageDir + docker.MountROOption,
6470
"-v", logsDir + ":" + logsDir + docker.MountRWOption,
6571
"-v", outputPathDir + ":" + outputPathDir + docker.MountRWOption,
6672
"-v", "/dev:/dev",
@@ -72,6 +78,17 @@ func buildDockerArgs(
7278
args = append(args, "-v", rpmSource.dir+":"+rpmSource.dir+docker.MountRWOption)
7379
}
7480

81+
if options.imageFile != "" {
82+
inputImageDir := path.Dir(options.imageFile)
83+
args = append(args, "-v", inputImageDir+":"+inputImageDir+docker.MountROOption)
84+
} else if options.imageTag != "" {
85+
// To be able to use the image tag, we need to set the entry point to
86+
// the script that will download the image first and then run the
87+
// Image Customizer after that. This overrides the default entry point
88+
// which calls the Image Customizer directly.
89+
args = append(args, entryPointSwitch, downloadAndCustomizeScriptPath)
90+
}
91+
7592
return args
7693
}
7794

@@ -105,18 +122,32 @@ func buildRpmSourcesInfo(rpmSources []string) []rpmSourceInfo {
105122
}
106123

107124
func buildImageCustomizerArgs(
108-
options *imageCustomizerOptions, buildDir, logFile, logLevel, logColor string, rpmSources []rpmSourceInfo,
125+
options *imageCustomizerOptions, imageCustomizerCommand, buildDir, logFile, logLevel, logColor string,
126+
rpmSources []rpmSourceInfo,
109127
) []string {
110-
args := []string{
128+
args := []string{}
129+
130+
if options.imageTag != "" {
131+
// /usr/local/bin/run.sh expects the first argument to be the image tag.
132+
// It then passes the rest to the image customizer binary as before.
133+
args = append(args, options.imageTag)
134+
}
135+
136+
args = append(args, imageCustomizerCommand)
137+
138+
if options.imageFile != "" {
139+
args = append(args, "--image-file", options.imageFile)
140+
}
141+
142+
args = append(args, []string{
111143
"--build-dir", buildDir,
112144
"--config-file", options.configFile,
113-
"--image-file", options.imageFile,
114145
"--output-image-format", options.outputImageFormat,
115146
"--output-path", options.outputPath,
116147
"--log-level", logLevel,
117148
"--log-color", logColor,
118149
"--log-file", logFile,
119-
}
150+
}...)
120151

121152
for _, rpmSource := range rpmSources {
122153
args = append(args, "--rpm-source", rpmSource.source)
@@ -134,10 +165,10 @@ func buildImageCustomizerArgs(
134165
}
135166

136167
func runImageCustomizerContainer(
137-
env *azldev.Env, command string, options *imageCustomizerOptions,
168+
env *azldev.Env, imageCustomizerCommand string, options *imageCustomizerOptions,
138169
) error {
139170
if env.DryRun() {
140-
return fmt.Errorf("dry-run mode is not supported for the 'image %s' command", command)
171+
return fmt.Errorf("dry-run mode is not supported for the 'image %s' command", imageCustomizerCommand)
141172
}
142173

143174
containerTag := env.Config().Tools.ImageCustomizer.ContainerTag
@@ -157,10 +188,6 @@ func runImageCustomizerContainer(
157188
logsDir = filepath.Join(env.ProjectDir(), projectgen.DefaultLogDir)
158189
}
159190

160-
configDir := path.Dir(options.configFile)
161-
inputImageDir := path.Dir(options.imageFile)
162-
outputPathDir := path.Dir(options.outputPath)
163-
164191
timestamp := time.Now().Format(logFileTimeFormat)
165192
logFile := path.Join(logsDir, fmt.Sprintf("image-customizer-%s.log", timestamp))
166193
logMarkers := getImageCustomizerInfoMarkers()
@@ -169,13 +196,13 @@ func runImageCustomizerContainer(
169196

170197
rpmSourcesInfo := buildRpmSourcesInfo(options.rpmSources)
171198

172-
dockerArgs := buildDockerArgs(buildDir, configDir, inputImageDir, logsDir, outputPathDir, rpmSourcesInfo)
199+
dockerArgs := buildDockerArgs(options, buildDir, logsDir, rpmSourcesInfo)
173200

174201
imageCustomizerArgs := buildImageCustomizerArgs(
175-
options, buildDir, logFile, defaultLogLevel, string(env.ColorMode()), rpmSourcesInfo,
176-
)
202+
options, imageCustomizerCommand, buildDir, logFile, defaultLogLevel, string(env.ColorMode()),
203+
rpmSourcesInfo)
177204

178-
_, err := docker.RunDocker(env.Context(), env, dockerArgs, containerTag, command, imageCustomizerArgs, logFile,
205+
_, err := docker.RunDocker(env.Context(), env, dockerArgs, containerTag, imageCustomizerArgs, logFile,
179206
func(_ context.Context, line string) {
180207
filterImageCustomizerOutput(env, line, logMarkers)
181208
},

internal/app/azldev/cmds/image/injectfiles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ func NewImageInjectFilesCmd() *cobra.Command {
5656
func injectFileIntoImage(
5757
env *azldev.Env, options *imageCustomizerOptions,
5858
) error {
59-
return runImageCustomizerContainer(env, imageCustomizerInject, options)
59+
return runImageCustomizerContainer(env, imageCustomizerInjectCmd, options)
6060
}

internal/utils/docker/docker.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717
MountRWOption string = ":z"
1818
MountROOption string = ":z,ro"
1919
PrivilegedFlag string = "--privileged"
20-
InteractiveFlag string = "-it"
20+
InteractiveFlag string = "-i"
2121
)
2222

2323
func validateDockerArgs(dockerArgs []string) error {
@@ -46,7 +46,7 @@ func validateDockerArgs(dockerArgs []string) error {
4646
// stdout output as a string. It can also capture output from a log file (logFile)
4747
// in real-time, filter it (logFilter) and send it to the console.
4848
func RunDocker(ctx context.Context, cmdFactory opctx.CmdFactory, dockerArgs []string,
49-
containerTag string, command string, commandArgs []string, logFile string,
49+
containerTag string, entryPointArgs []string, logFile string,
5050
logFilter func(context.Context, string),
5151
) (string, error) {
5252
err := validateDockerArgs(dockerArgs)
@@ -58,8 +58,7 @@ func RunDocker(ctx context.Context, cmdFactory opctx.CmdFactory, dockerArgs []st
5858

5959
args = append(args, dockerArgs...)
6060
args = append(args, containerTag)
61-
args = append(args, command)
62-
args = append(args, commandArgs...)
61+
args = append(args, entryPointArgs...)
6362

6463
var stderr strings.Builder
6564

scenario/docker/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ RUN groupadd -f -g "${GID}" mock && \
3232
RUN tdnf -y install \
3333
ca-certificates \
3434
mock \
35+
moby-containerd \
36+
moby-engine \
37+
docker-cli \
3538
sudo
3639

3740
COPY entrypoint.sh /usr/local/bin/entrypoint.sh

0 commit comments

Comments
 (0)