github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/bootstrapImage.go (about) 1 package builder 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/Cloud-Foundations/Dominator/lib/format" 16 "github.com/Cloud-Foundations/Dominator/lib/goroutine" 17 "github.com/Cloud-Foundations/Dominator/lib/image" 18 "github.com/Cloud-Foundations/Dominator/lib/srpc" 19 proto "github.com/Cloud-Foundations/Dominator/proto/imaginator" 20 ) 21 22 const ( 23 cmdPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 24 syscall.S_IROTH | syscall.S_IXOTH 25 dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 26 syscall.S_IROTH | syscall.S_IXOTH 27 packagerPathname = "/bin/generic-packager" 28 ) 29 30 var environmentToCopy = map[string]struct{}{ 31 "PATH": {}, 32 "TZ": {}, 33 "SHELL": {}, 34 } 35 36 var environmentToSet = map[string]string{ 37 "HOME": "/", 38 "LOGNAME": "root", 39 "USER": "root", 40 } 41 42 func cleanPackages(g *goroutine.Goroutine, rootDir string, 43 buildLog io.Writer) error { 44 fmt.Fprintln(buildLog, "\nCleaning packages:") 45 startTime := time.Now() 46 err := runInTarget(g, nil, buildLog, rootDir, nil, packagerPathname, 47 "clean") 48 if err != nil { 49 return errors.New("error cleaning: " + err.Error()) 50 } 51 fmt.Fprintf(buildLog, "Package clean took: %s\n", 52 format.Duration(time.Since(startTime))) 53 return nil 54 } 55 56 func clearResolvConf(g *goroutine.Goroutine, writer io.Writer, 57 rootDir string) error { 58 return runInTarget(g, nil, writer, rootDir, nil, 59 "/bin/cp", "/dev/null", "/etc/resolv.conf") 60 } 61 62 func makeTempDirectory(dir, prefix string) (string, error) { 63 tmpDir, err := ioutil.TempDir(dir, prefix) 64 if err != nil { 65 return "", err 66 } 67 if err := os.Chmod(tmpDir, dirPerms); err != nil { 68 os.RemoveAll(tmpDir) 69 return "", err 70 } 71 return tmpDir, nil 72 } 73 74 func (stream *bootstrapStream) build(b *Builder, client srpc.ClientI, 75 request proto.BuildImageRequest, 76 buildLog buildLogger) (*image.Image, error) { 77 startTime := time.Now() 78 args := make([]string, 0, len(stream.BootstrapCommand)) 79 rootDir, err := makeTempDirectory("", 80 strings.Replace(request.StreamName, "/", "_", -1)) 81 if err != nil { 82 return nil, err 83 } 84 defer os.RemoveAll(rootDir) 85 fmt.Fprintf(buildLog, "Created image working directory: %s\n", rootDir) 86 vg := variablesGetter(request.Variables).copy() 87 vg.add("dir", rootDir) 88 request.Variables = vg 89 for _, exp := range stream.BootstrapCommand { 90 arg := expandExpression(exp, func(name string) string { 91 return vg[name] 92 }) 93 args = append(args, arg) 94 } 95 fmt.Fprintf(buildLog, "Running command: %s with args:\n", args[0]) 96 for _, arg := range args[1:] { 97 fmt.Fprintf(buildLog, " %s\n", arg) 98 } 99 g, err := newNamespaceTarget() 100 if err != nil { 101 return nil, err 102 } 103 defer g.Quit() 104 err = runInTarget(g, nil, buildLog, "", nil, args[0], args[1:]...) 105 if err != nil { 106 return nil, err 107 } else { 108 packager := b.packagerTypes[stream.PackagerType] 109 if err := packager.writePackageInstaller(rootDir); err != nil { 110 return nil, err 111 } 112 if err := clearResolvConf(g, buildLog, rootDir); err != nil { 113 return nil, err 114 } 115 buildDuration := time.Since(startTime) 116 fmt.Fprintf(buildLog, "\nBuild time: %s\n", 117 format.Duration(buildDuration)) 118 if err := cleanPackages(g, rootDir, buildLog); err != nil { 119 return nil, err 120 } 121 return packImage(g, client, request, rootDir, 122 stream.Filter, nil, nil, stream.imageFilter, stream.imageTags, 123 stream.imageTriggers, b.mtimesCopyFilter, buildLog) 124 } 125 } 126 127 func (packager *packagerType) writePackageInstaller(rootDir string) error { 128 filename := filepath.Join(rootDir, packagerPathname) 129 file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, cmdPerms) 130 if err != nil { 131 return err 132 } 133 defer file.Close() 134 writer := bufio.NewWriter(file) 135 defer writer.Flush() 136 packager.writePackageInstallerContents(writer) 137 return writer.Flush() 138 } 139 140 func (packager *packagerType) writePackageInstallerContents(writer io.Writer) { 141 fmt.Fprintln(writer, "#! /bin/sh") 142 fmt.Fprintln(writer, "# Created by imaginator.") 143 for _, line := range packager.Verbatim { 144 fmt.Fprintln(writer, line) 145 } 146 fmt.Fprintln(writer, "cmd=\"$1\"; shift") 147 writePackagerCommand(writer, "clean", packager.CleanCommand) 148 fmt.Fprintln(writer, `[ "$cmd" = "copy-in" ] && exec cat > "$1"`) 149 writePackagerCommand(writer, "install", packager.InstallCommand) 150 writePackagerCommand(writer, "list", packager.ListCommand.ArgList) 151 writePackagerCommand(writer, "remove", packager.RemoveCommand) 152 fmt.Fprintln(writer, `[ "$cmd" = "run" ] && exec "$@"`) 153 multiplier := packager.ListCommand.SizeMultiplier 154 if multiplier < 1 { 155 multiplier = 1 156 } 157 fmt.Fprintf(writer, 158 "[ \"$cmd\" = \"show-size-multiplier\" ] && exec echo %d\n", multiplier) 159 writePackagerCommand(writer, "update", packager.UpdateCommand) 160 writePackagerCommand(writer, "upgrade", packager.UpgradeCommand) 161 fmt.Fprintln(writer, "echo \"Invalid command: $cmd\"") 162 fmt.Fprintln(writer, "exit 2") 163 } 164 165 func writePackagerCommand(writer io.Writer, cmd string, command []string) { 166 if len(command) < 1 { 167 fmt.Fprintf(writer, "[ \"$cmd\" = \"%s\" ] && exit 0\n", cmd) 168 } else { 169 fmt.Fprintf(writer, "[ \"$cmd\" = \"%s\" ] && exec", cmd) 170 for _, arg := range command { 171 writeArgument(writer, arg) 172 } 173 fmt.Fprintf(writer, " \"$@\"\n") 174 } 175 } 176 177 func writeArgument(writer io.Writer, arg string) { 178 if len(strings.Fields(arg)) < 2 { 179 fmt.Fprintf(writer, " %s", arg) 180 } else { 181 lenArg := len(arg) 182 if lenArg > 0 && arg[lenArg-1] == '\n' { 183 arg = arg[:lenArg-1] + `\n` 184 } 185 fmt.Fprintf(writer, " '%s'", arg) 186 } 187 }