github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/build/sources/conveyorPacker_zypper.go (about) 1 // Copyright (c) 2018-2019, 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 zypperConf = "/etc/zypp/zypp.conf" 26 ) 27 28 // ZypperConveyorPacker only needs to hold the bundle for the container 29 type ZypperConveyorPacker struct { 30 b *types.Bundle 31 } 32 33 // Get downloads container information from the specified source 34 func (cp *ZypperConveyorPacker) Get(b *types.Bundle) (err error) { 35 cp.b = b 36 37 // check for zypper on system 38 zypperPath, err := exec.LookPath("zypper") 39 if err != nil { 40 return fmt.Errorf("zypper is not in PATH: %v", err) 41 } 42 43 // check for rpm on system 44 err = rpmPathCheck() 45 if err != nil { 46 return 47 } 48 49 // get mirrorURL, OSVerison, and Includes components to definition 50 mirrorurl, ok := cp.b.Recipe.Header["mirrorurl"] 51 if !ok { 52 return fmt.Errorf("Invalid zypper header, no MirrorURL specified") 53 } 54 55 // look for an OS version if the mirror specifies it 56 osversion := "" 57 regex := regexp.MustCompile(`(?i)%{OSVERSION}`) 58 if regex.MatchString(mirrorurl) { 59 osversion, ok = cp.b.Recipe.Header["osversion"] 60 if !ok { 61 return fmt.Errorf("Invalid zypper header, OSVersion referenced in mirror but no OSVersion specified") 62 } 63 mirrorurl = regex.ReplaceAllString(mirrorurl, osversion) 64 } 65 66 include, _ := cp.b.Recipe.Header["include"] 67 68 // check for include environment variable and add it to requires string 69 include += ` ` + os.Getenv("INCLUDE") 70 71 // trim leading and trailing whitespace 72 include = strings.TrimSpace(include) 73 74 // add aaa_base to start of include list by default 75 include = `aaa_base ` + include 76 77 // Create the main portion of zypper config 78 err = cp.genZypperConfig() 79 if err != nil { 80 return fmt.Errorf("While generating Zypper config: %v", err) 81 } 82 83 err = cp.copyPseudoDevices() 84 if err != nil { 85 return fmt.Errorf("While copying pseudo devices: %v", err) 86 } 87 88 // Add mirrorURL as repo 89 cmd := exec.Command(zypperPath, `--root`, cp.b.Rootfs(), `ar`, mirrorurl, `repo-oss`) 90 cmd.Stdout = os.Stdout 91 cmd.Stderr = os.Stderr 92 if err = cmd.Run(); err != nil { 93 return fmt.Errorf("While adding zypper mirror: %v", err) 94 } 95 96 // Refreshing gpg keys 97 cmd = exec.Command(zypperPath, `--root`, cp.b.Rootfs(), `--gpg-auto-import-keys`, `refresh`) 98 cmd.Stdout = os.Stdout 99 cmd.Stderr = os.Stderr 100 if err = cmd.Run(); err != nil { 101 return fmt.Errorf("While refreshing gpg keys: %v", err) 102 } 103 104 args := []string{`--non-interactive`, `-c`, filepath.Join(cp.b.Rootfs(), zypperConf), `--root`, cp.b.Rootfs(), `--releasever=` + osversion, `-n`, `install`, `--auto-agree-with-licenses`, `--download-in-advance`} 105 args = append(args, strings.Fields(include)...) 106 107 // Zypper install command 108 cmd = exec.Command(zypperPath, args...) 109 cmd.Stdout = os.Stdout 110 cmd.Stderr = os.Stderr 111 112 sylog.Debugf("\n\tZypper Path: %s\n\tDetected Arch: %s\n\tOSVersion: %s\n\tMirrorURL: %s\n\tIncludes: %s\n", zypperPath, runtime.GOARCH, osversion, mirrorurl, include) 113 114 // run zypper 115 if err = cmd.Run(); err != nil { 116 return fmt.Errorf("While bootstrapping from zypper: %v", err) 117 } 118 119 return nil 120 } 121 122 // Pack puts relevant objects in a Bundle! 123 func (cp *ZypperConveyorPacker) Pack() (b *types.Bundle, err error) { 124 err = cp.insertBaseEnv() 125 if err != nil { 126 return nil, fmt.Errorf("While inserting base environment: %v", err) 127 } 128 129 err = cp.insertRunScript() 130 if err != nil { 131 return nil, fmt.Errorf("While inserting runscript: %v", err) 132 } 133 134 return cp.b, nil 135 } 136 137 func (cp *ZypperConveyorPacker) insertBaseEnv() (err error) { 138 if err = makeBaseEnv(cp.b.Rootfs()); err != nil { 139 return 140 } 141 return nil 142 } 143 144 func (cp *ZypperConveyorPacker) insertRunScript() (err error) { 145 f, err := os.Create(cp.b.Rootfs() + "/.singularity.d/runscript") 146 if err != nil { 147 return 148 } 149 150 defer f.Close() 151 152 _, err = f.WriteString("#!/bin/sh\n") 153 if err != nil { 154 return 155 } 156 157 if err != nil { 158 return 159 } 160 161 f.Sync() 162 163 err = os.Chmod(cp.b.Rootfs()+"/.singularity.d/runscript", 0755) 164 if err != nil { 165 return 166 } 167 168 return nil 169 } 170 171 func (cp *ZypperConveyorPacker) genZypperConfig() (err error) { 172 err = os.MkdirAll(filepath.Join(cp.b.Rootfs(), "/etc/zypp"), 0775) 173 if err != nil { 174 return fmt.Errorf("While creating %v: %v", filepath.Join(cp.b.Rootfs(), "/etc/zypp"), err) 175 } 176 177 err = ioutil.WriteFile(filepath.Join(cp.b.Rootfs(), zypperConf), []byte("[main]\ncachedir=/val/cache/zypp-bootstrap\n\n"), 0664) 178 if err != nil { 179 return 180 } 181 182 return nil 183 } 184 185 func (cp *ZypperConveyorPacker) copyPseudoDevices() (err error) { 186 err = os.Mkdir(filepath.Join(cp.b.Rootfs(), "/dev"), 0775) 187 if err != nil { 188 return fmt.Errorf("While creating %v: %v", filepath.Join(cp.b.Rootfs(), "/dev"), err) 189 } 190 191 devs := []string{"/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"} 192 193 for _, dev := range devs { 194 cmd := exec.Command("cp", "-a", dev, filepath.Join(cp.b.Rootfs(), "/dev")) 195 cmd.Stdout = os.Stdout 196 cmd.Stderr = os.Stderr 197 if err = cmd.Run(); err != nil { 198 f, err := os.Create(cp.b.Rootfs() + "/.singularity.d/runscript") 199 if err != nil { 200 return fmt.Errorf("While creating %v: %v", filepath.Join(cp.b.Rootfs(), dev), err) 201 } 202 203 defer f.Close() 204 } 205 } 206 207 return nil 208 } 209 210 func rpmPathCheck() (err error) { 211 var output, stderr bytes.Buffer 212 213 cmd := exec.Command("rpm", "--showrc") 214 cmd.Stdout = &output 215 cmd.Stderr = &stderr 216 217 if err = cmd.Run(); err != nil { 218 return fmt.Errorf("%v: %v", err, stderr.String()) 219 } 220 221 rpmDBPath := "" 222 scanner := bufio.NewScanner(&output) 223 scanner.Split(bufio.ScanLines) 224 225 for scanner.Scan() { 226 // search for dbpath from showrc output 227 if strings.Contains(scanner.Text(), "_dbpath\t") { 228 // second field in the string is the path 229 rpmDBPath = strings.Fields(scanner.Text())[2] 230 } 231 } 232 233 if rpmDBPath != `%{_var}/lib/rpm` { 234 return fmt.Errorf("RPM database is using a non-standard path: %s\n"+ 235 "There is a way to work around this problem:\n"+ 236 "Create a file at path %s/.rpmmacros.\n"+ 237 "Place the following lines into the '.rpmmacros' file:\n"+ 238 "%s\n"+ 239 "%s\n"+ 240 "After creating the file, re-run the bootstrap.\n"+ 241 "More info: https://github.com/sylabs/singularity/issues/241\n", 242 rpmDBPath, os.Getenv("HOME"), `%_var /var`, `%_dbpath %{_var}/lib/rpm`) 243 } 244 245 return nil 246 }