@@ -8,27 +8,139 @@ package buildpipeline
88import (
99 "fmt"
1010 "os"
11+ "os/exec"
1112 "path/filepath"
13+ "strings"
14+
15+ "golang.org/x/sys/unix"
1216
1317 "github.com/microsoft/azurelinux/toolkit/tools/internal/file"
1418 "github.com/microsoft/azurelinux/toolkit/tools/internal/logger"
15-
16- "golang.org/x/sys/unix"
19+ "github.com/microsoft/azurelinux/toolkit/tools/internal/shell"
1720)
1821
1922const (
20- rootBaseDirEnv = "CHROOT_DIR"
21- chrootLock = "chroot-pool.lock"
22- chrootUse = "chroot-used"
23+ rootBaseDirEnv = "CHROOT_DIR"
24+ chrootLock = "chroot-pool.lock"
25+ chrootUse = "chroot-used"
26+ systemdDetectVirtTool = "systemd-detect-virt"
2327)
2428
29+ var isRegularBuildCached * bool
30+
31+ // checkIfContainerDockerEnvFile checks if the tool is running in a Docker container by checking if /.dockerenv exists. This
32+ // check may not be reliable in all environments, so it is recommended to use systemd-detect-virt if available.
33+ func checkIfContainerDockerEnvFile () (bool , error ) {
34+ exists , err := file .PathExists ("/.dockerenv" )
35+ if err != nil {
36+ err = fmt .Errorf ("failed to check if /.dockerenv exists:\n %w" , err )
37+ return false , err
38+ }
39+ return exists , nil
40+ }
41+
42+ // checkIfContainerIgnoreDockerEnvFile checks if the user has placed a file in the root directory to ignore the Docker
43+ // environment check.
44+ func checkIfContainerIgnoreDockerEnvFile () (bool , error ) {
45+ ignoreDockerEnvExists , err := file .PathExists ("/.mariner-toolkit-ignore-dockerenv" )
46+ if err != nil {
47+ err = fmt .Errorf ("failed to check if /.mariner-toolkit-ignore-dockerenv exists:\n %w" , err )
48+ return false , err
49+ }
50+ return ignoreDockerEnvExists , nil
51+ }
52+
53+ // checkIfContainerSystemdDetectVirt uses systemd-detect-virt, a tool that can be used to detect if the system is running
54+ // in a virtualized environment. More specifically, using '-c' flag will detect container-based virtualization only.
55+ func checkIfContainerSystemdDetectVirt () (bool , error ) {
56+ // We should have the systemd-detect-virt command available in the environment, but check for it just in case since it
57+ // was previously not explicitly required for the toolkit.
58+ _ , err := exec .LookPath (systemdDetectVirtTool )
59+ if err != nil {
60+ err = fmt .Errorf ("failed to find %s in the PATH:\n %w" , systemdDetectVirtTool , err )
61+ return false , err
62+ }
63+
64+ // The tool will return error code 1 based on detection, we only care about the stdout so ignore the return code.
65+ stdout , _ , _ := shell .Execute (systemdDetectVirtTool , "-c" )
66+
67+ // There are several possible outputs from systemd-detect-virt we care about:
68+ // - none: Not running in a virtualized environment, easy
69+ // - wsl: Reports as a container, but we don't want to treat it as such. It should be able to handle regular builds
70+ // - anything else: We'll assume it's a container
71+ stdout = strings .TrimSpace (stdout )
72+ switch stdout {
73+ case "none" :
74+ logger .Log .Debugf ("Tool is not running in a container, systemd-detect-virt reports: '%s'" , stdout )
75+ return false , nil
76+ case "wsl" :
77+ logger .Log .Debugf ("Tool is running in WSL, treating as a non-container environment, systemd-detect-virt reports: '%s'" , stdout )
78+ return false , nil
79+ default :
80+ logger .Log .Debugf ("Tool is running in a container, systemd-detect-virt reports: '%s'" , stdout )
81+ return true , nil
82+ }
83+ }
84+
2585// IsRegularBuild indicates if it is a regular build (without using docker)
2686func IsRegularBuild () bool {
27- // some specific build pipeline builds Mariner from a Docker container and
28- // consequently have special requirements with regards to chroot
29- // check if .dockerenv file exist to disambiguate build pipeline
30- exists , _ := file .PathExists ("/.dockerenv" )
31- return ! exists
87+ if isRegularBuildCached != nil {
88+ return * isRegularBuildCached
89+ }
90+
91+ // If /.mariner-toolkit-ignore-dockerenv exists, then it is a regular build no matter what.
92+ hasIgnoreFile , err := checkIfContainerIgnoreDockerEnvFile ()
93+ if err != nil {
94+ // Log the error, but continue with the check.
95+ logger .Log .Warnf ("Failed to check if /.mariner-toolkit-ignore-dockerenv exists: %s" , err )
96+ }
97+ if hasIgnoreFile {
98+ isRegularBuild := true
99+ isRegularBuildCached = & isRegularBuild
100+ return isRegularBuild
101+ }
102+
103+ // There are multiple ways to detect if the build is running in a Docker container.
104+ // - Check with systemd-detect-virt tool first. This is the most reliable way.
105+ // - The legacy way is to check if /.dockerenv exists. However, this is not reliable
106+ // as it may not be present in all environments.
107+ // - If the user has set the CHROOT_DIR environment variable, then it is likely a Docker build.
108+ isRegularBuild := true
109+ isDockerContainer , err := checkIfContainerSystemdDetectVirt ()
110+ if err == nil {
111+ isRegularBuild = ! isDockerContainer
112+ if ! isRegularBuild {
113+ logger .Log .Info ("systemd-detect-virt reports that the tool is running in a container, running as a container build" )
114+ }
115+ } else {
116+ // Fallback if systemd-detect-virt isn't available.
117+ systemdErrMsg := err .Error ()
118+ isDockerContainer , err = checkIfContainerDockerEnvFile ()
119+ if err != nil {
120+ // Log the error, but continue with the check.
121+ logger .Log .Warnf ("Failed to check if /.dockerenv exists: %s" , err )
122+ } else {
123+ isRegularBuild = ! isDockerContainer
124+ }
125+ message := []string {
126+ "Failed to detect if the system is running in a container using systemd-detect-virt." ,
127+ systemdErrMsg ,
128+ "Checking if the system is running in a container by checking /.dockerenv." ,
129+ }
130+ if isRegularBuild {
131+ message = append (message , "Result: Not a container." )
132+ } else {
133+ message = append (message , "Result: Container detected." )
134+ }
135+ // logger.PrintMessageBox is not available in 2.0, so print each line separately.
136+ for _ , line := range message {
137+ logger .Log .Warn (line )
138+ }
139+ }
140+
141+ // Cache the result
142+ isRegularBuildCached = & isRegularBuild
143+ return isRegularBuild
32144}
33145
34146// GetChrootDir returns the chroot folder
@@ -42,7 +154,7 @@ func GetChrootDir(proposedDir string) (chrootDir string, err error) {
42154
43155 // In docker based pipeline pre-existing chroot pool is under a folder which path
44156 // is indicated by an env variable
45- chrootPoolFolder , varExist := unix . Getenv (rootBaseDirEnv )
157+ chrootPoolFolder , varExist := os . LookupEnv (rootBaseDirEnv )
46158 if ! varExist || len (chrootPoolFolder ) == 0 {
47159 err = fmt .Errorf ("env variable %s not defined" , rootBaseDirEnv )
48160 logger .Log .Errorf ("%s" , err .Error ())
0 commit comments