@@ -8,28 +8,157 @@ package buildpipeline
88import (
99 "fmt"
1010 "os"
11+ "os/exec"
1112 "path/filepath"
13+ "strings"
14+
15+ "github.com/sirupsen/logrus"
16+ "golang.org/x/sys/unix"
1217
1318 "github.com/microsoft/azurelinux/toolkit/tools/internal/file"
1419 "github.com/microsoft/azurelinux/toolkit/tools/internal/logger"
15-
16- "golang.org/x/sys/unix"
20+ "github.com/microsoft/azurelinux/toolkit/tools/internal/shell"
1721)
1822
1923const (
20- rootBaseDirEnv = "CHROOT_DIR"
21- chrootLock = "chroot-pool.lock"
22- chrootUse = "chroot-used"
24+ rootBaseDirEnv = "CHROOT_DIR"
25+ chrootLock = "chroot-pool.lock"
26+ chrootUse = "chroot-used"
27+ systemdDetectVirtTool = "systemd-detect-virt"
2328)
2429
30+ var isRegularBuildCached * bool
31+
32+ // checkIfContainerDockerEnvFile checks if the tool is running in a Docker container by checking if /.dockerenv exists. This
33+ // check may not be reliable in all environments, so it is recommended to use systemd-detect-virt if available.
34+ func checkIfContainerDockerEnvFile () (bool , error ) {
35+ exists , err := file .PathExists ("/.dockerenv" )
36+ if err != nil {
37+ err = fmt .Errorf ("failed to check if /.dockerenv exists:\n %w" , err )
38+ return false , err
39+ }
40+ return exists , nil
41+ }
42+
43+ // checkIfContainerIgnoreDockerEnvFile checks if the user has placed a file in the root directory to ignore the Docker
44+ // environment check.
45+ func checkIfContainerIgnoreDockerEnvFile () (bool , error ) {
46+ ignoreDockerEnvExists , err := file .PathExists ("/.mariner-toolkit-ignore-dockerenv" )
47+ if err != nil {
48+ err = fmt .Errorf ("failed to check if /.mariner-toolkit-ignore-dockerenv exists:\n %w" , err )
49+ return false , err
50+ }
51+ return ignoreDockerEnvExists , nil
52+ }
53+
54+ // checkIfContainerChrootDirEnv checks if the user has set the CHROOT_DIR environment variable, which is a requirement for
55+ // Docker-based builds. If the variable exists, it is likely that the tool is running in a Docker container.
56+ func checkIfContainerChrootDirEnv () bool {
57+ _ , exists := os .LookupEnv (rootBaseDirEnv )
58+ return exists
59+ }
60+
61+ // checkIfContainerSystemdDetectVirt uses systemd-detect-virt, a tool that can be used to detect if the system is running
62+ // in a virtualized environment. More specifically, using '-c' flag will detect container-based virtualization only.
63+ func checkIfContainerSystemdDetectVirt () (bool , error ) {
64+ // We should have the systemd-detect-virt command available in the environment, but check for it just in case since it
65+ // was previously not explicitly required for the toolkit.
66+ _ , err := exec .LookPath (systemdDetectVirtTool )
67+ if err != nil {
68+ err = fmt .Errorf ("failed to find %s in the PATH:\n %w" , systemdDetectVirtTool , err )
69+ return false , err
70+ }
71+
72+ // The tool will return error code 1 based on detection, we only care about the stdout so ignore the return code.
73+ stdout , _ , _ := shell .Execute (systemdDetectVirtTool , "-c" )
74+
75+ // There are several possible outputs from systemd-detect-virt we care about:
76+ // - none: Not running in a virtualized environment, easy
77+ // - wsl: Reports as a container, but we don't want to treat it as such. It should be able to handle regular builds
78+ // - anything else: We'll assume it's a container
79+ stdout = strings .TrimSpace (stdout )
80+ switch stdout {
81+ case "none" :
82+ logger .Log .Debugf ("Tool is not running in a container, systemd-detect-virt reports: '%s'" , stdout )
83+ return false , nil
84+ case "wsl" :
85+ logger .Log .Debugf ("Tool is running in WSL, treating as a non-container environment, systemd-detect-virt reports: '%s'" , stdout )
86+ return false , nil
87+ default :
88+ logger .Log .Debugf ("Tool is running in a container, systemd-detect-virt reports: '%s'" , stdout )
89+ return true , nil
90+ }
91+ }
92+
2593// IsRegularBuild indicates if it is a regular build (without using docker)
2694func IsRegularBuild () bool {
27- // some specific build pipeline builds Azure Linux 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- dockerEnvExists , _ := file .PathExists ("/.dockerenv" )
31- ignoreDockerEnvExists , _ := file .PathExists ("/.mariner-toolkit-ignore-dockerenv" )
32- return ignoreDockerEnvExists || ! dockerEnvExists
95+ if isRegularBuildCached != nil {
96+ return * isRegularBuildCached
97+ }
98+
99+ // If /.mariner-toolkit-ignore-dockerenv exists, then it is a regular build no matter what.
100+ hasIgnoreFile , err := checkIfContainerIgnoreDockerEnvFile ()
101+ if err != nil {
102+ // Log the error, but continue with the check.
103+ logger .Log .Warnf ("Failed to check if /.mariner-toolkit-ignore-dockerenv exists: %s" , err )
104+ }
105+ if hasIgnoreFile {
106+ isRegularBuild := true
107+ isRegularBuildCached = & isRegularBuild
108+ return isRegularBuild
109+ }
110+
111+ // There are multiple ways to detect if the build is running in a Docker container.
112+ // - Check with systemd-detect-virt tool first. This is the most reliable way.
113+ // - The legacy way is to check if /.dockerenv exists. However, this is not reliable
114+ // as it may not be present in all environments.
115+ // - If the user has set the CHROOT_DIR environment variable, then it is likely a Docker build.
116+ isRegularBuild := true
117+ isDockerContainer , err := checkIfContainerSystemdDetectVirt ()
118+ if err == nil {
119+ isRegularBuild = ! isDockerContainer
120+ if ! isRegularBuild {
121+ logger .Log .Info ("systemd-detect-virt reports that the tool is running in a container, running as a container build" )
122+ }
123+ } else {
124+ // Fallback if systemd-detect-virt isn't available.
125+ systemdErrMsg := err .Error ()
126+ isDockerContainer , err = checkIfContainerDockerEnvFile ()
127+ if err != nil {
128+ // Log the error, but continue with the check.
129+ logger .Log .Warnf ("Failed to check if /.dockerenv exists: %s" , err )
130+ } else {
131+ isRegularBuild = ! isDockerContainer
132+ }
133+ message := []string {
134+ "Failed to detect if the system is running in a container using systemd-detect-virt." ,
135+ systemdErrMsg ,
136+ "Checking if the system is running in a container by checking /.dockerenv." ,
137+ }
138+ if isRegularBuild {
139+ message = append (message , "Result: Not a container." )
140+ } else {
141+ message = append (message , "Result: Container detected." )
142+ }
143+ logger .PrintMessageBox (logrus .WarnLevel , message )
144+ }
145+
146+ // If the user set the CHROOT_DIR environment variable, but we don't detect a container, print a warning. This is
147+ // likely a misconfiguration, however trust the user and force the build to run as a container. If this is a mistake,
148+ // the tools should fail very quickly after this point.
149+ if checkIfContainerChrootDirEnv () && isRegularBuild {
150+ message := []string {
151+ "CHROOT_DIR is set, but the system is not detected as a container." ,
152+ "This is likely a misconfiguration!" ,
153+ "**Forcing the build to run as a container build**, however chroot operations may fail." ,
154+ }
155+ logger .PrintMessageBox (logrus .WarnLevel , message )
156+ isRegularBuild = false
157+ }
158+
159+ // Cache the result
160+ isRegularBuildCached = & isRegularBuild
161+ return isRegularBuild
33162}
34163
35164// GetChrootDir returns the chroot folder
@@ -43,7 +172,7 @@ func GetChrootDir(proposedDir string) (chrootDir string, err error) {
43172
44173 // In docker based pipeline pre-existing chroot pool is under a folder which path
45174 // is indicated by an env variable
46- chrootPoolFolder , varExist := unix . Getenv (rootBaseDirEnv )
175+ chrootPoolFolder , varExist := os . LookupEnv (rootBaseDirEnv )
47176 if ! varExist || len (chrootPoolFolder ) == 0 {
48177 err = fmt .Errorf ("env variable %s not defined" , rootBaseDirEnv )
49178 logger .Log .Errorf ("%s" , err .Error ())
0 commit comments