github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/processManifest.go (about) 1 package builder 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 "time" 11 12 "github.com/Cloud-Foundations/Dominator/lib/filesystem/util" 13 "github.com/Cloud-Foundations/Dominator/lib/filter" 14 "github.com/Cloud-Foundations/Dominator/lib/format" 15 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 16 "github.com/Cloud-Foundations/Dominator/lib/goroutine" 17 "github.com/Cloud-Foundations/Dominator/lib/json" 18 "github.com/Cloud-Foundations/Dominator/lib/srpc" 19 "github.com/Cloud-Foundations/Dominator/lib/verstr" 20 ) 21 22 func deleteDirectories(directoriesToDelete []string) error { 23 for index := len(directoriesToDelete) - 1; index >= 0; index-- { 24 if err := os.Remove(directoriesToDelete[index]); err != nil { 25 return err 26 } 27 } 28 return nil 29 } 30 31 func makeDirectory(directory string, directoriesToDelete []string, 32 directoriesWhichExist map[string]struct{}, 33 bindMountDirectories map[string]struct{}, 34 buildLog io.Writer) ([]string, error) { 35 if _, ok := directoriesWhichExist[directory]; ok { 36 return directoriesToDelete, nil 37 } else if fi, err := os.Stat(directory); err != nil { 38 if !os.IsNotExist(err) { 39 return directoriesToDelete, err 40 } 41 var err error 42 directoriesToDelete, err = makeDirectory(filepath.Dir(directory), 43 directoriesToDelete, directoriesWhichExist, bindMountDirectories, 44 buildLog) 45 if err != nil { 46 return directoriesToDelete, err 47 } 48 if _, ok := bindMountDirectories[directory]; ok { 49 fmt.Fprintf(buildLog, "Making bind mount point: %s\n", directory) 50 } else { 51 fmt.Fprintf(buildLog, 52 "Making intermediate directory for bind mount: %s\n", 53 directory) 54 } 55 if err := os.Mkdir(directory, fsutil.DirPerms); err != nil { 56 return nil, err 57 } 58 directoriesToDelete = append(directoriesToDelete, directory) 59 directoriesWhichExist[directory] = struct{}{} 60 return directoriesToDelete, nil 61 } else if !fi.IsDir() { 62 return directoriesToDelete, 63 fmt.Errorf("%s is not a directory", directory) 64 } else { 65 directoriesWhichExist[directory] = struct{}{} 66 return directoriesToDelete, nil 67 } 68 } 69 70 func makeMountPoints(rootDir string, bindMounts []string, 71 buildLog io.Writer) ([]string, error) { 72 var directoriesToDelete []string 73 directoriesWhichExist := make(map[string]struct{}) 74 defer deleteDirectories(directoriesToDelete) 75 bindMountDirectories := make(map[string]struct{}, len(bindMounts)) 76 for _, bindMount := range bindMounts { 77 bindMountDirectories[filepath.Join(rootDir, bindMount)] = struct{}{} 78 } 79 for _, bindMount := range bindMounts { 80 directory := filepath.Join(rootDir, bindMount) 81 var err error 82 directoriesToDelete, err = makeDirectory(directory, directoriesToDelete, 83 directoriesWhichExist, bindMountDirectories, buildLog) 84 if err != nil { 85 return nil, err 86 } 87 } 88 retval := directoriesToDelete 89 directoriesToDelete = nil // Do not clean up in the defer. 90 return retval, nil 91 } 92 93 // readManifestFile will read the manifest file in the manifest directory and 94 // will apply variable expansion to the source image name using envGetter if not 95 // nil. 96 func readManifestFile(manifestDir string, envGetter environmentGetter) ( 97 manifestConfigType, error) { 98 manifestFile := filepath.Join(manifestDir, "manifest") 99 var manifestConfig manifestConfigType 100 if err := json.ReadFromFile(manifestFile, &manifestConfig); err != nil { 101 return manifestConfigType{}, 102 errors.New("error reading manifest file: " + err.Error()) 103 } 104 if envGetter == nil { 105 return manifestConfig, nil 106 } 107 manifestConfig.SourceImage = expandExpression(manifestConfig.SourceImage, 108 func(name string) string { 109 return envGetter.getenv()[name] 110 }) 111 for key, value := range manifestConfig.SourceImageBuildVariables { 112 newValue := expandExpression(value, func(name string) string { 113 return envGetter.getenv()[name] 114 }) 115 manifestConfig.SourceImageBuildVariables[key] = newValue 116 } 117 manifestConfig.SourceImageGitCommitId = expandExpression( 118 manifestConfig.SourceImageGitCommitId, 119 func(name string) string { 120 return envGetter.getenv()[name] 121 }) 122 for _, values := range manifestConfig.SourceImageTagsToMatch { 123 for index, value := range values { 124 newValue := expandExpression(value, func(name string) string { 125 return envGetter.getenv()[name] 126 }) 127 values[index] = newValue 128 } 129 } 130 return manifestConfig, nil 131 } 132 133 func unpackImageAndProcessManifest(client srpc.ClientI, manifestDir string, 134 maxSourceAge time.Duration, rootDir string, bindMounts []string, 135 applyFilter bool, envGetter environmentGetter, 136 buildLog io.Writer) (manifestType, error) { 137 manifestConfig, err := readManifestFile(manifestDir, envGetter) 138 if err != nil { 139 return manifestType{}, err 140 } 141 var mtimesCopyAddFilter, mtimesCopyFilter *filter.Filter 142 if len(manifestConfig.MtimesCopyAddFilterLines) > 0 { 143 mtimesCopyAddFilter, err = filter.New( 144 manifestConfig.MtimesCopyAddFilterLines) 145 if err != nil { 146 return manifestType{}, err 147 } 148 } 149 if len(manifestConfig.MtimesCopyFilterLines) > 0 { 150 mtimesCopyFilter, err = filter.New(manifestConfig.MtimesCopyFilterLines) 151 if err != nil { 152 return manifestType{}, err 153 } 154 } 155 sourceImageInfo, err := unpackImage(client, manifestConfig.SourceImage, 156 manifestConfig.SourceImageGitCommitId, 157 manifestConfig.SourceImageTagsToMatch, 158 maxSourceAge, rootDir, buildLog) 159 if err != nil { 160 var buildError *buildErrorType 161 if errors.As(err, &buildError) { 162 buildError.sourceImageBuildVariables = 163 manifestConfig.SourceImageBuildVariables 164 } 165 return manifestType{}, fmt.Errorf("error unpacking image: %w", err) 166 } 167 startTime := time.Now() 168 err = processManifest(manifestDir, rootDir, bindMounts, envGetter, buildLog) 169 if err != nil { 170 return manifestType{}, 171 errors.New("error processing manifest: " + err.Error()) 172 } 173 if applyFilter && manifestConfig.Filter != nil { 174 err := util.DeletedFilteredFiles(rootDir, manifestConfig.Filter) 175 if err != nil { 176 return manifestType{}, err 177 } 178 } 179 fmt.Fprintf(buildLog, "Processed manifest in %s\n", 180 format.Duration(time.Since(startTime))) 181 return manifestType{ 182 filter: manifestConfig.Filter, 183 mtimesCopyAddFilter: mtimesCopyAddFilter, 184 mtimesCopyFilter: mtimesCopyFilter, 185 sourceImageInfo: sourceImageInfo, 186 }, nil 187 } 188 189 func processManifest(manifestDir, rootDir string, bindMounts []string, 190 envGetter environmentGetter, buildLog io.Writer) error { 191 // Copy in system /etc/resolv.conf 192 file, err := os.Open("/etc/resolv.conf") 193 if err != nil { 194 return err 195 } 196 defer file.Close() 197 for index, bindMount := range bindMounts { 198 bindMounts[index] = filepath.Clean(bindMount) 199 } 200 directoriesToDelete, err := makeMountPoints(rootDir, bindMounts, buildLog) 201 if err != nil { 202 return err 203 } 204 defer func() { // Need to evaluate directoriesToDelete in deferred func. 205 deleteDirectories(directoriesToDelete) 206 }() 207 g, err := newNamespaceTargetWithMounts(rootDir, bindMounts) 208 if err != nil { 209 return err 210 } 211 defer g.Quit() 212 err = runInTarget(g, file, buildLog, rootDir, envGetter, 213 packagerPathname, "copy-in", "/etc/resolv.conf") 214 if err != nil { 215 return fmt.Errorf("error copying in /etc/resolv.conf: %s", err) 216 } 217 if err := copyFiles(manifestDir, "files", rootDir, buildLog); err != nil { 218 return err 219 } 220 err = runScripts(g, manifestDir, "pre-install-scripts", rootDir, envGetter, 221 buildLog) 222 if err != nil { 223 return err 224 } 225 packageList, err := fsutil.LoadLines(filepath.Join(manifestDir, 226 "package-list")) 227 if err != nil { 228 if !os.IsNotExist(err) { 229 return err 230 } 231 } 232 if len(packageList) > 0 { 233 err := updatePackageDatabase(g, rootDir, envGetter, buildLog) 234 if err != nil { 235 return err 236 } 237 } 238 err = installPackages(g, packageList, rootDir, envGetter, buildLog) 239 if err != nil { 240 return errors.New("error installing packages: " + err.Error()) 241 } 242 err = copyFiles(manifestDir, "post-install-files", rootDir, buildLog) 243 if err != nil { 244 return err 245 } 246 err = runScripts(g, manifestDir, "scripts", rootDir, envGetter, buildLog) 247 if err != nil { 248 return err 249 } 250 if err := cleanPackages(g, rootDir, buildLog); err != nil { 251 return err 252 } 253 if err := clearResolvConf(g, buildLog, rootDir); err != nil { 254 return err 255 } 256 err = copyFiles(manifestDir, "post-scripts-files", rootDir, buildLog) 257 if err != nil { 258 return err 259 } 260 if err := deleteDirectories(directoriesToDelete); err != nil { 261 return err 262 } 263 directoriesToDelete = nil 264 err = runScripts(nil, manifestDir, "post-cleanup-scripts", rootDir, 265 envGetter, buildLog) 266 if err != nil { 267 return err 268 } 269 return nil 270 } 271 272 func copyFiles(manifestDir, dirname, rootDir string, buildLog io.Writer) error { 273 startTime := time.Now() 274 sourceDir := filepath.Join(manifestDir, dirname) 275 cf := func(destFilename, sourceFilename string, mode os.FileMode) error { 276 return copyFile(destFilename, sourceFilename, mode, len(manifestDir)+1, 277 buildLog) 278 } 279 if err := fsutil.CopyTreeWithCopyFunc(rootDir, sourceDir, cf); err != nil { 280 return fmt.Errorf("error copying %s: %s", dirname, err) 281 } 282 fmt.Fprintf(buildLog, "\nCopied %s tree in %s\n", 283 dirname, format.Duration(time.Since(startTime))) 284 return nil 285 } 286 287 func copyFile(destFilename, sourceFilename string, mode os.FileMode, 288 prefixLength int, buildLog io.Writer) error { 289 same, err := fsutil.CompareFiles(destFilename, sourceFilename) 290 if err != nil && !os.IsNotExist(err) { 291 return err 292 } 293 if same { 294 fmt.Fprintf(buildLog, "Same contents for: %s\n", 295 sourceFilename[prefixLength:]) 296 return nil 297 } 298 return fsutil.CopyFile(destFilename, sourceFilename, mode) 299 } 300 301 func installPackages(g *goroutine.Goroutine, packageList []string, 302 rootDir string, envGetter environmentGetter, buildLog io.Writer) error { 303 if len(packageList) < 1 { // Nothing to do. 304 fmt.Fprintln(buildLog, "\nNo packages to install") 305 return nil 306 } 307 fmt.Fprintln(buildLog, "\nUpgrading packages:") 308 startTime := time.Now() 309 err := runInTarget(g, nil, buildLog, rootDir, envGetter, 310 packagerPathname, "upgrade") 311 if err != nil { 312 return errors.New("error upgrading: " + err.Error()) 313 } 314 fmt.Fprintf(buildLog, "Package upgrade took: %s\n", 315 format.Duration(time.Since(startTime))) 316 317 fmt.Fprintln(buildLog, "\nInstalling packages:", 318 strings.Join(packageList, " ")) 319 startTime = time.Now() 320 args := []string{"install"} 321 args = append(args, packageList...) 322 err = runInTarget(g, nil, buildLog, rootDir, envGetter, 323 packagerPathname, args...) 324 if err != nil { 325 return errors.New("error installing: " + err.Error()) 326 } 327 fmt.Fprintf(buildLog, "Package install took: %s\n", 328 format.Duration(time.Since(startTime))) 329 return nil 330 } 331 332 func runScripts(g *goroutine.Goroutine, manifestDir, dirname, rootDir string, 333 envGetter environmentGetter, buildLog io.Writer) error { 334 scriptsDir := filepath.Join(manifestDir, dirname) 335 file, err := os.Open(scriptsDir) 336 if err != nil { 337 if os.IsNotExist(err) { 338 fmt.Fprintf(buildLog, "No %s directory\n", dirname) 339 return nil 340 } 341 return err 342 } 343 names, err := file.Readdirnames(-1) 344 file.Close() 345 if err != nil { 346 return err 347 } 348 if len(names) < 1 { 349 fmt.Fprintln(buildLog, "\nNo scripts to run") 350 return nil 351 } 352 verstr.Sort(names) 353 fmt.Fprintf(buildLog, "\nRunning scripts in: %s\n", dirname) 354 scriptsStartTime := time.Now() 355 tmpDir := filepath.Join(rootDir, ".scripts") 356 if err := os.Mkdir(tmpDir, dirPerms); err != nil { 357 return err 358 } 359 defer os.RemoveAll(tmpDir) 360 for _, name := range names { 361 if len(name) > 0 && name[0] == '.' { 362 continue // Skip hidden paths. 363 } 364 err := fsutil.CopyFile(filepath.Join(tmpDir, name), 365 filepath.Join(scriptsDir, name), 366 dirPerms) 367 if err != nil { 368 return err 369 } 370 } 371 if g == nil { 372 g, err = newNamespaceTargetWithMounts(rootDir, nil) 373 if err != nil { 374 return err 375 } 376 defer g.Quit() 377 } 378 for _, name := range names { 379 fmt.Fprintf(buildLog, "Running script: %s\n", name) 380 startTime := time.Now() 381 err := runInTarget(g, nil, buildLog, rootDir, envGetter, 382 packagerPathname, "run", filepath.Join("/.scripts", name)) 383 if err != nil { 384 return errors.New("error running script: " + name + ": " + 385 err.Error()) 386 } 387 timeTaken := time.Since(startTime) 388 fmt.Fprintf(buildLog, "Script: %s took %s\n", 389 name, format.Duration(timeTaken)) 390 fmt.Fprintln(buildLog, 391 "=================================================================") 392 } 393 timeTaken := time.Since(scriptsStartTime) 394 fmt.Fprintf(buildLog, "Ran scripts in %s\n", format.Duration(timeTaken)) 395 return nil 396 } 397 398 func updatePackageDatabase(g *goroutine.Goroutine, rootDir string, 399 envGetter environmentGetter, buildLog io.Writer) error { 400 fmt.Fprintln(buildLog, "\nUpdating package database:") 401 startTime := time.Now() 402 err := runInTarget(g, nil, buildLog, rootDir, envGetter, 403 packagerPathname, "update") 404 if err != nil { 405 return errors.New("error updating: " + err.Error()) 406 } 407 fmt.Fprintf(buildLog, "Package databse update took: %s\n", 408 format.Duration(time.Since(startTime))) 409 return nil 410 }