github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/build/sources/conveyorPacker_yum.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package sources 7 8 import ( 9 "bufio" 10 "bytes" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "strings" 19 20 "github.com/sylabs/singularity/internal/pkg/sylog" 21 "github.com/sylabs/singularity/pkg/build/types" 22 ) 23 24 const ( 25 yumConf = "/etc/bootstrap-yum.conf" 26 ) 27 28 // YumConveyor holds stuff that needs to be packed into the bundle 29 type YumConveyor struct { 30 b *types.Bundle 31 rpmPath string 32 mirrorurl string 33 updateurl string 34 osversion string 35 include string 36 gpg string 37 httpProxy string 38 } 39 40 // YumConveyorPacker only needs to hold the conveyor to have the needed data to pack 41 type YumConveyorPacker struct { 42 YumConveyor 43 } 44 45 // Get downloads container information from the specified source 46 func (c *YumConveyor) Get(b *types.Bundle) (err error) { 47 c.b = b 48 49 // check for dnf or yum on system 50 var installCommandPath string 51 if installCommandPath, err = exec.LookPath("dnf"); err == nil { 52 sylog.Debugf("Found dnf at: %v", installCommandPath) 53 } else if installCommandPath, err = exec.LookPath("yum"); err == nil { 54 sylog.Debugf("Found yum at: %v", installCommandPath) 55 } else { 56 return fmt.Errorf("Neither yum nor dnf in PATH") 57 } 58 59 // check for rpm on system 60 err = c.getRPMPath() 61 if err != nil { 62 return fmt.Errorf("While checking rpm path: %v", err) 63 } 64 65 err = c.getBootstrapOptions() 66 if err != nil { 67 return fmt.Errorf("While getting bootstrap options: %v", err) 68 } 69 70 err = c.genYumConfig() 71 if err != nil { 72 return fmt.Errorf("While generating Yum config: %v", err) 73 } 74 75 err = c.copyPseudoDevices() 76 if err != nil { 77 return fmt.Errorf("While copying pseudo devices: %v", err) 78 } 79 80 args := []string{`--noplugins`, `-c`, filepath.Join(c.b.Rootfs(), yumConf), `--installroot`, c.b.Rootfs(), `--releasever=` + c.osversion, `-y`, `install`} 81 args = append(args, strings.Fields(c.include)...) 82 83 // Do the install 84 sylog.Debugf("\n\tInstall Command Path: %s\n\tDetected Arch: %s\n\tOSVersion: %s\n\tMirrorURL: %s\n\tUpdateURL: %s\n\tIncludes: %s\n", installCommandPath, runtime.GOARCH, c.osversion, c.mirrorurl, c.updateurl, c.include) 85 cmd := exec.Command(installCommandPath, args...) 86 // cmd.Stdout = os.Stdout 87 cmd.Stderr = os.Stderr 88 if err = cmd.Run(); err != nil { 89 return fmt.Errorf("While bootstrapping: %v", err) 90 } 91 92 // clean up bootstrap packages 93 os.RemoveAll(filepath.Join(c.b.Rootfs(), "/var/cache/yum-bootstrap")) 94 95 return nil 96 } 97 98 // Pack puts relevant objects in a Bundle! 99 func (cp *YumConveyorPacker) Pack() (b *types.Bundle, err error) { 100 err = cp.insertBaseEnv() 101 if err != nil { 102 return nil, fmt.Errorf("While inserting base environment: %v", err) 103 } 104 105 err = cp.insertRunScript() 106 if err != nil { 107 return nil, fmt.Errorf("While inserting runscript: %v", err) 108 } 109 110 return cp.b, nil 111 } 112 113 func (c *YumConveyor) getRPMPath() (err error) { 114 var output, stderr bytes.Buffer 115 116 c.rpmPath, err = exec.LookPath("rpm") 117 if err != nil { 118 return fmt.Errorf("RPM is not in PATH: %v", err) 119 } 120 121 cmd := exec.Command("rpm", "--showrc") 122 cmd.Stdout = &output 123 cmd.Stderr = &stderr 124 125 if err = cmd.Run(); err != nil { 126 return fmt.Errorf("%v: %v", err, stderr.String()) 127 } 128 129 rpmDBPath := "" 130 scanner := bufio.NewScanner(&output) 131 scanner.Split(bufio.ScanLines) 132 133 for scanner.Scan() { 134 // search for dbpath from showrc output 135 if strings.Contains(scanner.Text(), "_dbpath\t") { 136 // second field in the string is the path 137 rpmDBPath = strings.Fields(scanner.Text())[2] 138 } 139 } 140 141 if rpmDBPath == "" { 142 return fmt.Errorf("Could not find dbpath") 143 } else if rpmDBPath != `%{_var}/lib/rpm` { 144 return fmt.Errorf("RPM database is using a weird path: %s\n"+ 145 "You are probably running this bootstrap on Debian or Ubuntu.\n"+ 146 "There is a way to work around this problem:\n"+ 147 "Create a file at path %s/.rpmmacros.\n"+ 148 "Place the following lines into the '.rpmmacros' file:\n"+ 149 "%s\n"+ 150 "%s\n"+ 151 "After creating the file, re-run the bootstrap.\n"+ 152 "More info: https://github.com/sylabs/singularity/issues/241\n", 153 rpmDBPath, os.Getenv("HOME"), `%_var /var`, `%_dbpath %{_var}/lib/rpm`) 154 } 155 156 return nil 157 } 158 159 func (c *YumConveyor) getBootstrapOptions() (err error) { 160 var ok bool 161 162 // look for http_proxy and gpg environment vars 163 c.gpg = os.Getenv("GPG") 164 c.httpProxy = os.Getenv("http_proxy") 165 166 // get mirrorURL, updateURL, OSVerison, and Includes components to definition 167 c.mirrorurl, ok = c.b.Recipe.Header["mirrorurl"] 168 if !ok { 169 return fmt.Errorf("Invalid yum header, no MirrorURL specified") 170 } 171 172 c.updateurl, _ = c.b.Recipe.Header["updateurl"] 173 174 // look for an OS version if a mirror specifies it 175 regex := regexp.MustCompile(`(?i)%{OSVERSION}`) 176 if regex.MatchString(c.mirrorurl) || regex.MatchString(c.updateurl) { 177 c.osversion, ok = c.b.Recipe.Header["osversion"] 178 if !ok { 179 return fmt.Errorf("Invalid yum header, OSVersion referenced in mirror but no OSVersion specified") 180 } 181 c.mirrorurl = regex.ReplaceAllString(c.mirrorurl, c.osversion) 182 c.updateurl = regex.ReplaceAllString(c.updateurl, c.osversion) 183 } 184 185 include, _ := c.b.Recipe.Header["include"] 186 187 // check for include environment variable and add it to requires string 188 include += ` ` + os.Getenv("INCLUDE") 189 190 // trim leading and trailing whitespace 191 include = strings.TrimSpace(include) 192 193 // add aa_base to start of include list by default 194 include = `/etc/redhat-release coreutils ` + include 195 196 c.include = include 197 198 return nil 199 } 200 201 func (c *YumConveyor) genYumConfig() (err error) { 202 fileContent := "[main]\n" 203 // http proxy 204 if c.httpProxy != "" { 205 fileContent += "proxy=" + c.httpProxy + "\n" 206 } 207 fileContent += "cachedir=/var/cache/yum-bootstrap\n" 208 fileContent += "keepcache=0\n" 209 fileContent += "debuglevel=2\n" 210 fileContent += "logfile=/var/log/yum.log\n" 211 fileContent += "syslog_device=/dev/null\n" 212 fileContent += "exactarch=1\n" 213 fileContent += "obsoletes=1\n" 214 // gpg 215 if c.gpg != "" { 216 fileContent += "gpgcheck=1\n" 217 } else { 218 fileContent += "gpgcheck=0\n" 219 } 220 fileContent += "plugins=1\n" 221 fileContent += "reposdir=0\n" 222 fileContent += "deltarpm=0\n" 223 fileContent += "\n" 224 fileContent += "[base]\n" 225 fileContent += "name=Linux $releasever - $basearch\n" 226 // mirror 227 if c.mirrorurl != "" { 228 fileContent += "baseurl=" + c.mirrorurl + "\n" 229 } 230 fileContent += "enabled=1\n" 231 // gpg 232 if c.gpg != "" { 233 fileContent += "gpgcheck=1\n" 234 } else { 235 fileContent += "gpgcheck=0\n" 236 } 237 238 // add update section if updateurl is specified 239 if c.updateurl != "" { 240 fileContent += "[updates]\n" 241 fileContent += "name=Linux $releasever - $basearch updates\n" 242 fileContent += "baseurl=" + c.updateurl + "\n" 243 fileContent += "enabled=1\n" 244 // gpg 245 if c.gpg != "" { 246 fileContent += "gpgcheck=1\n" 247 } else { 248 fileContent += "gpgcheck=0\n" 249 } 250 fileContent += "\n" 251 } 252 253 err = os.Mkdir(filepath.Join(c.b.Rootfs(), "/etc"), 0775) 254 if err != nil { 255 return fmt.Errorf("While creating %v: %v", filepath.Join(c.b.Rootfs(), "/etc"), err) 256 } 257 258 err = ioutil.WriteFile(filepath.Join(c.b.Rootfs(), yumConf), []byte(fileContent), 0664) 259 if err != nil { 260 return fmt.Errorf("While creating %v: %v", filepath.Join(c.b.Rootfs(), yumConf), err) 261 } 262 263 // if gpg key is specified, import it 264 if c.gpg != "" { 265 err = c.importGPGKey() 266 if err != nil { 267 return fmt.Errorf("While importing GPG key: %v", err) 268 } 269 } else { 270 sylog.Infof("Skipping GPG Key Import") 271 } 272 273 return nil 274 } 275 276 func (c *YumConveyor) importGPGKey() (err error) { 277 sylog.Infof("We have a GPG key! Preparing RPM database.") 278 279 // make sure gpg is being imported over https 280 if strings.HasPrefix(c.gpg, "https://") == false { 281 return fmt.Errorf("GPG key must be fetched with https") 282 } 283 284 // make sure curl is installed so rpm can import gpg key 285 if _, err = exec.LookPath("curl"); err != nil { 286 return fmt.Errorf("Neither yum nor dnf in PATH") 287 } 288 289 cmd := exec.Command(c.rpmPath, "--root", c.b.Rootfs(), "--initdb") 290 cmd.Stdout = os.Stdout 291 cmd.Stderr = os.Stderr 292 if err = cmd.Run(); err != nil { 293 return fmt.Errorf("While initializing new rpm db: %v", err) 294 } 295 296 cmd = exec.Command(c.rpmPath, "--root", c.b.Rootfs(), "--import", c.gpg) 297 cmd.Stdout = os.Stdout 298 cmd.Stderr = os.Stderr 299 if err = cmd.Run(); err != nil { 300 return fmt.Errorf("While importing GPG key with rpm: %v", err) 301 } 302 303 sylog.Infof("GPG key import complete!") 304 305 return nil 306 } 307 308 func (c *YumConveyor) copyPseudoDevices() (err error) { 309 err = os.Mkdir(filepath.Join(c.b.Rootfs(), "/dev"), 0775) 310 if err != nil { 311 return fmt.Errorf("While creating %v: %v", filepath.Join(c.b.Rootfs(), "/dev"), err) 312 } 313 314 devs := []string{"/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"} 315 316 for _, dev := range devs { 317 cmd := exec.Command("cp", "-a", dev, filepath.Join(c.b.Rootfs(), "/dev")) 318 cmd.Stdout = os.Stdout 319 cmd.Stderr = os.Stderr 320 if err = cmd.Run(); err != nil { 321 f, err := os.Create(c.b.Rootfs() + "/.singularity.d/runscript") 322 if err != nil { 323 return fmt.Errorf("While creating %v: %v", filepath.Join(c.b.Rootfs(), dev), err) 324 } 325 326 defer f.Close() 327 } 328 } 329 330 return nil 331 } 332 333 func (cp *YumConveyorPacker) insertBaseEnv() (err error) { 334 if err = makeBaseEnv(cp.b.Rootfs()); err != nil { 335 return 336 } 337 return nil 338 } 339 340 func (cp *YumConveyorPacker) insertRunScript() (err error) { 341 ioutil.WriteFile(filepath.Join(cp.b.Rootfs(), "/.singularity.d/runscript"), []byte("#!/bin/sh\n"), 0755) 342 if err != nil { 343 return 344 } 345 346 return nil 347 }