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  }