github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imagebuilder/builder/addImage.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "sort" 12 "time" 13 14 imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client" 15 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 16 "github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner" 17 "github.com/Cloud-Foundations/Dominator/lib/filesystem/util" 18 "github.com/Cloud-Foundations/Dominator/lib/filter" 19 "github.com/Cloud-Foundations/Dominator/lib/format" 20 "github.com/Cloud-Foundations/Dominator/lib/hash" 21 "github.com/Cloud-Foundations/Dominator/lib/image" 22 objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client" 23 "github.com/Cloud-Foundations/Dominator/lib/srpc" 24 "github.com/Cloud-Foundations/Dominator/lib/triggers" 25 proto "github.com/Cloud-Foundations/Dominator/proto/imaginator" 26 ) 27 28 const timeFormat = "2006-01-02:15:04:05" 29 30 var errorTestTimedOut = errors.New("test timed out") 31 32 type hasher struct { 33 objQ *objectclient.ObjectAdderQueue 34 } 35 36 type testResultType struct { 37 buffer chan byte 38 duration time.Duration 39 err error 40 prog string 41 } 42 43 func (h *hasher) Hash(reader io.Reader, length uint64) ( 44 hash.Hash, error) { 45 hash, err := h.objQ.Add(reader, length) 46 if err != nil { 47 return hash, errors.New("error sending image data: " + err.Error()) 48 } 49 return hash, nil 50 } 51 52 func addImage(client *srpc.Client, request proto.BuildImageRequest, 53 img *image.Image) (string, error) { 54 if request.ExpiresIn > 0 { 55 img.ExpiresAt = time.Now().Add(request.ExpiresIn) 56 } 57 name := path.Join(request.StreamName, time.Now().Format(timeFormat)) 58 if err := imageclient.AddImage(client, name, img); err != nil { 59 return "", errors.New("remote error: " + err.Error()) 60 } 61 return name, nil 62 } 63 64 func buildFileSystem(client *srpc.Client, dirname string, 65 scanFilter *filter.Filter) ( 66 *filesystem.FileSystem, error) { 67 var h hasher 68 var err error 69 h.objQ, err = objectclient.NewObjectAdderQueue(client) 70 if err != nil { 71 return nil, err 72 } 73 fs, err := buildFileSystemWithHasher(dirname, &h, scanFilter) 74 if err != nil { 75 h.objQ.Close() 76 return nil, err 77 } 78 err = h.objQ.Close() 79 if err != nil { 80 return nil, err 81 } 82 return fs, nil 83 } 84 85 func buildFileSystemWithHasher(dirname string, h *hasher, 86 scanFilter *filter.Filter) ( 87 *filesystem.FileSystem, error) { 88 fs, err := scanner.ScanFileSystem(dirname, nil, scanFilter, nil, h, nil) 89 if err != nil { 90 return nil, err 91 } 92 return &fs.FileSystem, nil 93 } 94 95 func listPackages(rootDir string) ([]image.Package, error) { 96 output := new(bytes.Buffer) 97 err := runInTarget(nil, output, rootDir, nil, packagerPathname, 98 "show-size-multiplier") 99 if err != nil { 100 return nil, fmt.Errorf("error getting size multiplier: %s", err) 101 } 102 sizeMultiplier := uint64(1) 103 nScanned, err := fmt.Fscanf(output, "%d", &sizeMultiplier) 104 if err != nil { 105 if err != io.EOF { 106 return nil, fmt.Errorf( 107 "error decoding size multiplier: %s", err) 108 } 109 } else if nScanned != 1 { 110 return nil, errors.New("malformed size multiplier") 111 } 112 output.Reset() 113 err = runInTarget(nil, output, rootDir, nil, packagerPathname, "list") 114 if err != nil { 115 return nil, err 116 } 117 packageMap := make(map[string]image.Package) 118 for { 119 var name, version string 120 var size uint64 121 nScanned, err := fmt.Fscanf(output, "%s %s %d\n", 122 &name, &version, &size) 123 if err != nil { 124 if err == io.EOF { 125 break 126 } 127 return nil, err 128 } 129 if nScanned != 3 { 130 return nil, errors.New("malformed line") 131 } 132 packageMap[name] = image.Package{ 133 Name: name, 134 Size: size * sizeMultiplier, 135 Version: version, 136 } 137 } 138 packageNames := make([]string, 0, len(packageMap)) 139 for name := range packageMap { 140 packageNames = append(packageNames, name) 141 } 142 sort.Strings(packageNames) 143 var packages []image.Package 144 for _, name := range packageNames { 145 packages = append(packages, packageMap[name]) 146 } 147 return packages, nil 148 } 149 150 func packImage(client *srpc.Client, request proto.BuildImageRequest, 151 dirname string, scanFilter *filter.Filter, 152 computedFilesList []util.ComputedFile, imageFilter *filter.Filter, 153 trig *triggers.Triggers, buildLog buildLogger) (*image.Image, error) { 154 packages, err := listPackages(dirname) 155 if err != nil { 156 return nil, fmt.Errorf("error listing packages: %s", err) 157 } 158 buildStartTime := time.Now() 159 fs, err := buildFileSystem(client, dirname, scanFilter) 160 if err != nil { 161 return nil, fmt.Errorf("error building file-system: %s", err) 162 } 163 if err := util.SpliceComputedFiles(fs, computedFilesList); err != nil { 164 return nil, fmt.Errorf("error splicing computed files: %s", err) 165 } 166 fs.ComputeTotalDataBytes() 167 duration := time.Since(buildStartTime) 168 speed := uint64(float64(fs.TotalDataBytes) / duration.Seconds()) 169 fmt.Fprintf(buildLog, 170 "Scanned file-system and uploaded %d objects (%s) in %s (%s/s)\n", 171 len(fs.InodeTable), format.FormatBytes(fs.TotalDataBytes), 172 format.Duration(duration), format.FormatBytes(speed)) 173 _, oldImage, err := getLatestImage(client, request.StreamName, buildLog) 174 if err != nil { 175 return nil, fmt.Errorf("error getting latest image: %s", err) 176 } else if oldImage != nil { 177 patchStartTime := time.Now() 178 util.CopyMtimes(oldImage.FileSystem, fs) 179 fmt.Fprintf(buildLog, "Copied mtimes in %s\n", 180 format.Duration(time.Since(patchStartTime))) 181 } 182 if err := runTests(dirname, buildLog); err != nil { 183 return nil, err 184 } 185 objClient := objectclient.AttachObjectClient(client) 186 // Make a copy of the build log because AddObject() drains the buffer. 187 logReader := bytes.NewBuffer(buildLog.Bytes()) 188 hashVal, _, err := objClient.AddObject(logReader, uint64(logReader.Len()), 189 nil) 190 if err != nil { 191 return nil, err 192 } 193 if err := objClient.Close(); err != nil { 194 return nil, err 195 } 196 img := &image.Image{ 197 BuildLog: &image.Annotation{Object: &hashVal}, 198 FileSystem: fs, 199 Filter: imageFilter, 200 Triggers: trig, 201 Packages: packages, 202 } 203 if err := img.Verify(); err != nil { 204 return nil, err 205 } 206 return img, nil 207 } 208 209 func runTests(rootDir string, buildLog buildLogger) error { 210 var testProgrammes []string 211 err := filepath.Walk(filepath.Join(rootDir, "tests"), 212 func(path string, fi os.FileInfo, err error) error { 213 if fi == nil || !fi.Mode().IsRegular() || fi.Mode()&0100 == 0 { 214 return nil 215 } 216 testProgrammes = append(testProgrammes, path[len(rootDir):]) 217 return nil 218 }) 219 if err != nil { 220 return err 221 } 222 if len(testProgrammes) < 1 { 223 return nil 224 } 225 fmt.Fprintf(buildLog, "Running %d tests\n", len(testProgrammes)) 226 results := make(chan testResultType, 1) 227 for _, prog := range testProgrammes { 228 go func(prog string) { 229 results <- runTest(rootDir, prog) 230 }(prog) 231 } 232 numFailures := 0 233 for range testProgrammes { 234 result := <-results 235 io.Copy(buildLog, &result) 236 if result.err != nil { 237 fmt.Fprintf(buildLog, "error running: %s: %s\n", 238 result.prog, result.err) 239 numFailures++ 240 } else { 241 fmt.Fprintf(buildLog, "%s passed in %s\n", 242 result.prog, format.Duration(result.duration)) 243 } 244 fmt.Fprintln(buildLog) 245 } 246 if numFailures > 0 { 247 return fmt.Errorf("%d tests failed", numFailures) 248 } 249 return nil 250 } 251 252 func runTest(rootDir, prog string) testResultType { 253 startTime := time.Now() 254 result := testResultType{ 255 buffer: make(chan byte, 4096), 256 prog: prog, 257 } 258 errChannel := make(chan error, 1) 259 timer := time.NewTimer(time.Second * 10) 260 go func() { 261 errChannel <- runInTarget(nil, &result, rootDir, nil, packagerPathname, 262 "run", prog) 263 }() 264 select { 265 case result.err = <-errChannel: 266 result.duration = time.Since(startTime) 267 case <-timer.C: 268 result.err = errorTestTimedOut 269 } 270 return result 271 } 272 273 func (w *testResultType) Read(p []byte) (int, error) { 274 for count := 0; count < len(p); count++ { 275 select { 276 case p[count] = <-w.buffer: 277 default: 278 return count, io.EOF 279 } 280 } 281 return len(p), nil 282 } 283 284 func (w *testResultType) Write(p []byte) (int, error) { 285 for index, ch := range p { 286 select { 287 case w.buffer <- ch: 288 default: 289 return index, io.ErrShortWrite 290 } 291 } 292 return len(p), nil 293 }