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 }