github.com/singularityware/singularity@v3.1.1+incompatible/pkg/ocibundle/sif/bundle.go (about)

     1  // Copyright (c) 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 sifbundle
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  
    16  	imageSpecs "github.com/opencontainers/image-spec/specs-go/v1"
    17  	specs "github.com/opencontainers/runtime-spec/specs-go"
    18  	"github.com/opencontainers/runtime-tools/generate"
    19  	"github.com/sylabs/singularity/pkg/image"
    20  	"github.com/sylabs/singularity/pkg/ocibundle"
    21  	"github.com/sylabs/singularity/pkg/ocibundle/tools"
    22  )
    23  
    24  type sifBundle struct {
    25  	image      string
    26  	bundlePath string
    27  	writable   bool
    28  	ocibundle.Bundle
    29  }
    30  
    31  func (s *sifBundle) writeConfig(img *image.Image, g *generate.Generator) error {
    32  	// check if SIF file contain an OCI image configuration
    33  	reader, err := image.NewSectionReader(img, "oci-config.json", -1)
    34  	if err != nil && err != image.ErrNoSection {
    35  		return fmt.Errorf("failed to read oci-config.json section: %s", err)
    36  	} else if err == image.ErrNoSection {
    37  		return tools.SaveBundleConfig(s.bundlePath, g)
    38  	}
    39  
    40  	var imgConfig imageSpecs.ImageConfig
    41  
    42  	if err := json.NewDecoder(reader).Decode(&imgConfig); err != nil {
    43  		return fmt.Errorf("failed to decode oci-config.json: %s", err)
    44  	}
    45  
    46  	if len(g.Config.Process.Args) == 1 && g.Config.Process.Args[0] == tools.RunScript {
    47  		args := imgConfig.Entrypoint
    48  		args = append(args, imgConfig.Cmd...)
    49  		if len(args) > 0 {
    50  			g.SetProcessArgs(args)
    51  		}
    52  	}
    53  
    54  	if g.Config.Process.Cwd == "" && imgConfig.WorkingDir != "" {
    55  		g.SetProcessCwd(imgConfig.WorkingDir)
    56  	}
    57  	for _, e := range imgConfig.Env {
    58  		found := false
    59  		k := strings.SplitN(e, "=", 2)
    60  		for _, pe := range g.Config.Process.Env {
    61  			if strings.HasPrefix(pe, k[0]+"=") {
    62  				found = true
    63  				break
    64  			}
    65  		}
    66  		if !found {
    67  			g.AddProcessEnv(k[0], k[1])
    68  		}
    69  	}
    70  
    71  	volumes := tools.Volumes(s.bundlePath).Path()
    72  	for dst := range imgConfig.Volumes {
    73  		replacer := strings.NewReplacer(string(os.PathSeparator), "_")
    74  		src := filepath.Join(volumes, replacer.Replace(dst))
    75  		if err := os.MkdirAll(src, 0755); err != nil {
    76  			return fmt.Errorf("failed to create volume directory %s: %s", src, err)
    77  		}
    78  		g.AddMount(specs.Mount{
    79  			Source:      src,
    80  			Destination: dst,
    81  			Type:        "none",
    82  			Options:     []string{"bind", "rw"},
    83  		})
    84  	}
    85  
    86  	return tools.SaveBundleConfig(s.bundlePath, g)
    87  }
    88  
    89  // Create creates an OCI bundle from a SIF image
    90  func (s *sifBundle) Create(ociConfig *specs.Spec) error {
    91  	if s.image == "" {
    92  		return fmt.Errorf("image wasn't set, need one to create bundle")
    93  	}
    94  
    95  	img, err := image.Init(s.image, s.writable)
    96  	if err != nil {
    97  		return fmt.Errorf("failed to load SIF image %s: %s", s.image, err)
    98  	}
    99  	defer img.File.Close()
   100  
   101  	if img.Type != image.SIF {
   102  		return fmt.Errorf("%s is not a SIF image", s.image)
   103  	}
   104  	if img.Partitions[0].Type != image.SQUASHFS {
   105  		return fmt.Errorf("unsupported image fs type: %v", img.Partitions[0].Type)
   106  	}
   107  	offset := img.Partitions[0].Offset
   108  	size := img.Partitions[0].Size
   109  
   110  	// generate OCI bundle directory and config
   111  	g, err := tools.GenerateBundleConfig(s.bundlePath, ociConfig)
   112  	if err != nil {
   113  		return fmt.Errorf("failed to generate OCI bundle/config: %s", err)
   114  	}
   115  
   116  	// associate SIF image with a block
   117  	loop, err := tools.CreateLoop(img.File, offset, size)
   118  	if err != nil {
   119  		tools.DeleteBundle(s.bundlePath)
   120  		return fmt.Errorf("failed to find loop device: %s", err)
   121  	}
   122  
   123  	rootFs := tools.RootFs(s.bundlePath).Path()
   124  	if err := syscall.Mount(loop, rootFs, "squashfs", syscall.MS_RDONLY, "errors=remount-ro"); err != nil {
   125  		tools.DeleteBundle(s.bundlePath)
   126  		return fmt.Errorf("failed to mount SIF partition: %s", err)
   127  	}
   128  
   129  	if err := s.writeConfig(img, g); err != nil {
   130  		// best effort to release loop device
   131  		syscall.Unmount(rootFs, syscall.MNT_DETACH)
   132  		tools.DeleteBundle(s.bundlePath)
   133  		return fmt.Errorf("failed to write OCI configuration: %s", err)
   134  	}
   135  
   136  	if s.writable {
   137  		if err := tools.CreateOverlay(s.bundlePath); err != nil {
   138  			// best effort to release loop device
   139  			syscall.Unmount(rootFs, syscall.MNT_DETACH)
   140  			tools.DeleteBundle(s.bundlePath)
   141  			return fmt.Errorf("failed to create overlay: %s", err)
   142  		}
   143  	}
   144  	return nil
   145  }
   146  
   147  // Delete erases OCI bundle create from SIF image
   148  func (s *sifBundle) Delete() error {
   149  	if s.writable {
   150  		if err := tools.DeleteOverlay(s.bundlePath); err != nil {
   151  			return fmt.Errorf("delete error: %s", err)
   152  		}
   153  	}
   154  	// Umount rootfs
   155  	rootFsDir := tools.RootFs(s.bundlePath).Path()
   156  	if err := syscall.Unmount(rootFsDir, syscall.MNT_DETACH); err != nil {
   157  		return fmt.Errorf("failed to unmount %s: %s", rootFsDir, err)
   158  	}
   159  	// delete bundle directory
   160  	return tools.DeleteBundle(s.bundlePath)
   161  }
   162  
   163  // FromSif returns a bundle interface to create/delete OCI bundle from SIF image
   164  func FromSif(image, bundle string, writable bool) (ocibundle.Bundle, error) {
   165  	var err error
   166  
   167  	s := &sifBundle{
   168  		writable: writable,
   169  	}
   170  	s.bundlePath, err = filepath.Abs(bundle)
   171  	if err != nil {
   172  		return nil, fmt.Errorf("failed to determine bundle path: %s", err)
   173  	}
   174  	if image != "" {
   175  		s.image, err = filepath.Abs(image)
   176  		if err != nil {
   177  			return nil, fmt.Errorf("failed to determine image path: %s", err)
   178  		}
   179  	}
   180  	return s, nil
   181  }