image/sif/internal/sif_util.go

235 lines
5.7 KiB
Go

//go:build linux
// +build linux
package internal
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/sylabs/sif/v2/pkg/sif"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type SifImage struct {
fimg *sif.FileImage
rootfs sif.Descriptor
deffile *sif.Descriptor
defReader io.Reader
cmdlist []string
runscript *bytes.Buffer
env *sif.Descriptor
envReader io.Reader
envlist []string
}
func LoadSIFImage(path string) (image SifImage, err error) {
// open up the SIF file and get its header
image.fimg, err = sif.LoadContainerFromPath(path, sif.OptLoadWithFlag(os.O_RDONLY))
if err != nil {
return
}
// check for a system partition and save it
image.rootfs, err = image.fimg.GetDescriptor(sif.WithPartitionType(sif.PartPrimSys))
if err != nil {
return SifImage{}, errors.Wrap(err, "looking up rootfs from SIF file")
}
// look for a definition file object
resultDesc, err := image.fimg.GetDescriptor(sif.WithDataType(sif.DataDeffile))
if err == nil {
// we assume in practice that typical SIF files don't hold multiple deffiles
image.deffile = &resultDesc
image.defReader = resultDesc.GetReader()
}
if err = image.generateConfig(); err != nil {
return SifImage{}, err
}
// look for an environment variable set object
resultDesc, err = image.fimg.GetDescriptor(sif.WithDataType(sif.DataEnvVar))
if err == nil {
// we assume in practice that typical SIF files don't hold multiple EnvVar sets
image.env = &resultDesc
image.envReader = resultDesc.GetReader()
}
return image, nil
}
func (image *SifImage) parseEnvironment(scanner *bufio.Scanner) error {
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if s == "" || strings.HasPrefix(s, "#") {
continue
}
if strings.HasPrefix(s, "%") {
return nil
}
image.envlist = append(image.envlist, s)
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "parsing environment from SIF definition file object")
}
return nil
}
func (image *SifImage) parseRunscript(scanner *bufio.Scanner) error {
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(s, "%") {
return nil
}
image.cmdlist = append(image.cmdlist, s)
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "parsing runscript from SIF definition file object")
}
return nil
}
func (image *SifImage) generateRunscript() error {
base := `#!/bin/bash
`
image.runscript = bytes.NewBufferString(base)
for _, s := range image.envlist {
_, err := image.runscript.WriteString(fmt.Sprintln(s))
if err != nil {
return errors.Wrap(err, "writing to runscript buffer")
}
}
for _, s := range image.cmdlist {
_, err := image.runscript.WriteString(fmt.Sprintln(s))
if err != nil {
return errors.Wrap(err, "writing to runscript buffer")
}
}
return nil
}
func (image *SifImage) generateConfig() error {
if image.deffile == nil {
image.cmdlist = append(image.cmdlist, "bash")
return nil
}
// extract %environment/%runscript from definition file
var err error
scanner := bufio.NewScanner(image.defReader)
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
again:
if s == `%environment` {
if err = image.parseEnvironment(scanner); err != nil {
return err
}
} else if s == `%runscript` {
if err = image.parseRunscript(scanner); err != nil {
return err
}
}
s = strings.TrimSpace(scanner.Text())
if s == `%environment` || s == `%runscript` {
goto again
}
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "reading lines from SIF definition file object")
}
if len(image.cmdlist) == 0 && len(image.envlist) == 0 {
image.cmdlist = append(image.cmdlist, "bash")
} else {
if err = image.generateRunscript(); err != nil {
return errors.Wrap(err, "generating runscript")
}
image.cmdlist = []string{"/podman/runscript"}
}
return nil
}
func (image SifImage) GetConfig(config *imgspecv1.Image) error {
config.Config.Cmd = append(config.Config.Cmd, image.cmdlist...)
return nil
}
func (image SifImage) UnloadSIFImage() (err error) {
err = image.fimg.UnloadContainer()
return
}
func (image SifImage) GetSIFID() string {
return image.fimg.ID()
}
func (image SifImage) GetSIFArch() string {
return image.fimg.PrimaryArch()
}
const squashFilename = "rootfs.squashfs"
const tarFilename = "rootfs.tar"
func runUnSquashFSTar(tempdir string) (err error) {
script := `
#!/bin/sh
unsquashfs -f ` + squashFilename + ` && tar --acls --xattrs -C ./squashfs-root -cpf ` + tarFilename + ` ./
`
if err = ioutil.WriteFile(filepath.Join(tempdir, "script"), []byte(script), 0755); err != nil {
return err
}
cmd := []string{"fakeroot", "--", "./script"}
xcmd := exec.Command(cmd[0], cmd[1:]...)
xcmd.Stderr = os.Stderr
xcmd.Dir = tempdir
err = xcmd.Run()
return
}
func (image *SifImage) writeRunscript(tempdir string) (err error) {
if image.runscript == nil {
return nil
}
rsPath := filepath.Join(tempdir, "squashfs-root", "podman")
if err = os.MkdirAll(rsPath, 0755); err != nil {
return
}
if err = ioutil.WriteFile(filepath.Join(rsPath, "runscript"), image.runscript.Bytes(), 0755); err != nil {
return errors.Wrap(err, "writing /podman/runscript")
}
return nil
}
func (image SifImage) SquashFSToTarLayer(tempdir string) (tarpath string, err error) {
f, err := os.Create(filepath.Join(tempdir, squashFilename))
if err != nil {
return
}
defer f.Close()
if _, err = io.CopyN(f, image.rootfs.GetReader(), image.rootfs.Size()); err != nil {
return
}
if err = f.Sync(); err != nil {
return
}
if err = image.writeRunscript(tempdir); err != nil {
return
}
if err = runUnSquashFSTar(tempdir); err != nil {
return
}
return filepath.Join(tempdir, tarFilename), nil
}