github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/imagetool/addImageCommon.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bufio" 6 "compress/gzip" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "os/exec" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/Cloud-Foundations/Dominator/imageserver/client" 17 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 18 "github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner" 19 "github.com/Cloud-Foundations/Dominator/lib/filesystem/untar" 20 "github.com/Cloud-Foundations/Dominator/lib/filter" 21 "github.com/Cloud-Foundations/Dominator/lib/hash" 22 "github.com/Cloud-Foundations/Dominator/lib/image" 23 "github.com/Cloud-Foundations/Dominator/lib/mbr" 24 objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client" 25 "github.com/Cloud-Foundations/Dominator/lib/srpc" 26 "github.com/Cloud-Foundations/Dominator/lib/srpc/setupclient" 27 "github.com/Cloud-Foundations/Dominator/lib/triggers" 28 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 29 ) 30 31 type hasher struct { 32 objQ *objectclient.ObjectAdderQueue 33 } 34 35 func addImage(imageSClient *srpc.Client, name string, img *image.Image) error { 36 if *expiresIn > 0 { 37 img.ExpiresAt = time.Now().Add(*expiresIn) 38 } else { 39 img.ExpiresAt = time.Time{} 40 } 41 if err := img.Verify(); err != nil { 42 return err 43 } 44 if err := img.VerifyRequiredPaths(requiredPaths); err != nil { 45 return err 46 } 47 if err := client.AddImage(imageSClient, name, img); err != nil { 48 return errors.New("remote error: " + err.Error()) 49 } 50 return nil 51 } 52 53 func (h *hasher) Hash(reader io.Reader, length uint64) ( 54 hash.Hash, error) { 55 hash, err := h.objQ.Add(reader, length) 56 if err != nil { 57 return hash, errors.New("error sending image data: " + err.Error()) 58 } 59 return hash, nil 60 } 61 62 func buildImage(imageSClient *srpc.Client, filter *filter.Filter, 63 imageFilename string) (*filesystem.FileSystem, error) { 64 var h hasher 65 var err error 66 h.objQ, err = objectclient.NewObjectAdderQueue(imageSClient) 67 if err != nil { 68 return nil, err 69 } 70 fs, err := buildImageWithHasher(imageSClient, filter, imageFilename, &h) 71 if err != nil { 72 h.objQ.Close() 73 return nil, err 74 } 75 err = h.objQ.Close() 76 if err != nil { 77 return nil, err 78 } 79 return fs, nil 80 } 81 82 func buildImageWithHasher(imageSClient *srpc.Client, filter *filter.Filter, 83 imageFilename string, h scanner.Hasher) (*filesystem.FileSystem, error) { 84 fi, err := os.Lstat(imageFilename) 85 if err != nil { 86 return nil, err 87 } 88 if fi.IsDir() { 89 sfs, err := scanner.ScanFileSystem(imageFilename, nil, filter, nil, h, 90 nil) 91 if err != nil { 92 return nil, err 93 } 94 return &sfs.FileSystem, nil 95 } 96 imageFile, err := os.Open(imageFilename) 97 if err != nil { 98 return nil, errors.New("error opening image file: " + err.Error()) 99 } 100 defer imageFile.Close() 101 if partitionTable, err := mbr.Decode(imageFile); err != nil { 102 if err != io.EOF { 103 return nil, err 104 } // Else perhaps a tiny tarfile, definitely not a partition table. 105 } else if partitionTable != nil { 106 return buildImageFromRaw(imageSClient, filter, imageFile, 107 partitionTable, h) 108 } 109 var imageReader io.Reader 110 if strings.HasSuffix(imageFilename, ".tar") { 111 imageReader = imageFile 112 } else if strings.HasSuffix(imageFilename, ".tar.gz") || 113 strings.HasSuffix(imageFilename, ".tgz") { 114 gzipReader, err := gzip.NewReader(imageFile) 115 if err != nil { 116 return nil, errors.New( 117 "error creating gzip reader: " + err.Error()) 118 } 119 defer gzipReader.Close() 120 imageReader = gzipReader 121 } else { 122 return nil, errors.New("unrecognised image type") 123 } 124 tarReader := tar.NewReader(imageReader) 125 fs, err := untar.Decode(tarReader, h, filter) 126 if err != nil { 127 return nil, errors.New("error building image: " + err.Error()) 128 } 129 return fs, nil 130 } 131 132 func buildImageFromRaw(imageSClient *srpc.Client, filter *filter.Filter, 133 imageFile *os.File, partitionTable *mbr.Mbr, 134 h scanner.Hasher) (*filesystem.FileSystem, error) { 135 var index uint 136 var offsetOfLargest, sizeOfLargest uint64 137 numPartitions := partitionTable.GetNumPartitions() 138 for index = 0; index < numPartitions; index++ { 139 offset := partitionTable.GetPartitionOffset(index) 140 size := partitionTable.GetPartitionSize(index) 141 if size > sizeOfLargest { 142 offsetOfLargest = offset 143 sizeOfLargest = size 144 } 145 } 146 if sizeOfLargest < 1 { 147 return nil, errors.New("unable to find largest partition") 148 } 149 if err := wsyscall.UnshareMountNamespace(); err != nil { 150 if os.IsPermission(err) { 151 // Try again with sudo(8). 152 args := make([]string, 0, len(os.Args)+1) 153 if sudoPath, err := exec.LookPath("sudo"); err != nil { 154 return nil, err 155 } else { 156 args = append(args, sudoPath) 157 } 158 if myPath, err := exec.LookPath(os.Args[0]); err != nil { 159 return nil, err 160 } else { 161 args = append(args, myPath) 162 } 163 args = append(args, fmt.Sprintf("-certDirectory=%s", 164 setupclient.GetCertDirectory())) 165 args = append(args, os.Args[1:]...) 166 if err := syscall.Exec(args[0], args, os.Environ()); err != nil { 167 return nil, errors.New("unable to Exec: " + err.Error()) 168 } 169 } 170 return nil, errors.New( 171 "error unsharing mount namespace: " + err.Error()) 172 } 173 cmd := exec.Command("mount", "-o", 174 fmt.Sprintf("loop,offset=%d", offsetOfLargest), imageFile.Name(), 175 "/mnt") 176 cmd.Stdin = os.Stdin 177 cmd.Stdout = os.Stdout 178 cmd.Stderr = os.Stderr 179 if err := cmd.Run(); err != nil { 180 return nil, err 181 } 182 fs, err := buildImageWithHasher(imageSClient, filter, "/mnt", h) 183 syscall.Unmount("/mnt", 0) 184 return fs, err 185 } 186 187 func loadImageFiles(image *image.Image, objectClient *objectclient.ObjectClient, 188 filterFilename, triggersFilename string) error { 189 var err error 190 if filterFilename != "" { 191 image.Filter, err = filter.Load(filterFilename) 192 if err != nil { 193 return err 194 } 195 } 196 if triggersFilename != "" { 197 image.Triggers, err = triggers.Load(triggersFilename) 198 if err != nil { 199 return err 200 } 201 } 202 image.BuildLog, err = getAnnotation(objectClient, *buildLog) 203 if err != nil { 204 return err 205 } 206 image.ReleaseNotes, err = getAnnotation(objectClient, *releaseNotes) 207 if err != nil { 208 return err 209 } 210 return nil 211 } 212 213 func getAnnotation(objectClient *objectclient.ObjectClient, name string) ( 214 *image.Annotation, error) { 215 if name == "" { 216 return nil, nil 217 } 218 file, err := os.Open(name) 219 if err != nil { 220 return &image.Annotation{URL: name}, nil 221 } 222 defer file.Close() 223 fi, err := file.Stat() 224 if err != nil { 225 return nil, err 226 } 227 reader := bufio.NewReader(file) 228 hash, _, err := objectClient.AddObject(reader, uint64(fi.Size()), nil) 229 return &image.Annotation{Object: &hash}, err 230 }