github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imagebuilder/builder/bootstrapImage.go (about) 1 // +build go1.10 2 3 package builder 4 5 import ( 6 "bufio" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "sort" 15 "strings" 16 "syscall" 17 "time" 18 19 "github.com/Cloud-Foundations/Dominator/lib/format" 20 "github.com/Cloud-Foundations/Dominator/lib/image" 21 "github.com/Cloud-Foundations/Dominator/lib/srpc" 22 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 23 proto "github.com/Cloud-Foundations/Dominator/proto/imaginator" 24 ) 25 26 const ( 27 cmdPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 28 syscall.S_IROTH | syscall.S_IXOTH 29 dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 30 syscall.S_IROTH | syscall.S_IXOTH 31 packagerPathname = "/bin/generic-packager" 32 ) 33 34 var environmentToCopy = map[string]struct{}{ 35 "PATH": {}, 36 "TZ": {}, 37 "SHELL": {}, 38 } 39 40 var environmentToSet = map[string]string{ 41 "HOME": "/", 42 "LOGNAME": "root", 43 "USER": "root", 44 } 45 46 func cleanPackages(rootDir string, buildLog io.Writer) error { 47 fmt.Fprintln(buildLog, "\nCleaning packages:") 48 startTime := time.Now() 49 err := runInTarget(nil, buildLog, rootDir, nil, packagerPathname, "clean") 50 if err != nil { 51 return errors.New("error cleaning: " + err.Error()) 52 } 53 fmt.Fprintf(buildLog, "Package clean took: %s\n", 54 format.Duration(time.Since(startTime))) 55 return nil 56 } 57 58 func makeTempDirectory(dir, prefix string) (string, error) { 59 tmpDir, err := ioutil.TempDir(dir, prefix) 60 if err != nil { 61 return "", err 62 } 63 if err := os.Chmod(tmpDir, dirPerms); err != nil { 64 os.RemoveAll(tmpDir) 65 return "", err 66 } 67 return tmpDir, nil 68 } 69 70 func (stream *bootstrapStream) build(b *Builder, client *srpc.Client, 71 request proto.BuildImageRequest, 72 buildLog buildLogger) (*image.Image, error) { 73 startTime := time.Now() 74 args := make([]string, 0, len(stream.BootstrapCommand)) 75 rootDir, err := makeTempDirectory("", 76 strings.Replace(request.StreamName, "/", "_", -1)) 77 if err != nil { 78 return nil, err 79 } 80 defer os.RemoveAll(rootDir) 81 fmt.Fprintf(buildLog, "Created image working directory: %s\n", rootDir) 82 for _, arg := range stream.BootstrapCommand { 83 if arg == "$dir" { 84 arg = rootDir 85 } 86 args = append(args, arg) 87 } 88 cmd := exec.Command(args[0], args[1:]...) 89 cmd.Stdout = buildLog 90 cmd.Stderr = buildLog 91 if err := cmd.Run(); err != nil { 92 return nil, err 93 } else { 94 packager := b.packagerTypes[stream.PackagerType] 95 if err := packager.writePackageInstaller(rootDir); err != nil { 96 return nil, err 97 } 98 if err := clearResolvConf(buildLog, rootDir); err != nil { 99 return nil, err 100 } 101 buildDuration := time.Since(startTime) 102 fmt.Fprintf(buildLog, "\nBuild time: %s\n", 103 format.Duration(buildDuration)) 104 if err := cleanPackages(rootDir, buildLog); err != nil { 105 return nil, err 106 } 107 return packImage(client, request, rootDir, 108 stream.Filter, nil, stream.imageFilter, stream.imageTriggers, 109 buildLog) 110 } 111 } 112 113 func (packager *packagerType) writePackageInstaller(rootDir string) error { 114 filename := filepath.Join(rootDir, packagerPathname) 115 file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, cmdPerms) 116 if err != nil { 117 return err 118 } 119 defer file.Close() 120 writer := bufio.NewWriter(file) 121 defer writer.Flush() 122 packager.writePackageInstallerContents(writer) 123 return writer.Flush() 124 } 125 126 func (packager *packagerType) writePackageInstallerContents(writer io.Writer) { 127 fmt.Fprintln(writer, "#! /bin/sh") 128 fmt.Fprintln(writer, "# Created by imaginator.") 129 fmt.Fprintln(writer, "mount -n none -t proc /proc") 130 fmt.Fprintln(writer, "mount -n none -t sysfs /sys") 131 for _, line := range packager.Verbatim { 132 fmt.Fprintln(writer, line) 133 } 134 fmt.Fprintln(writer, "cmd=\"$1\"; shift") 135 writePackagerCommand(writer, "clean", packager.CleanCommand) 136 fmt.Fprintln(writer, `[ "$cmd" = "copy-in" ] && exec cat > "$1"`) 137 writePackagerCommand(writer, "install", packager.InstallCommand) 138 writePackagerCommand(writer, "list", packager.ListCommand.ArgList) 139 writePackagerCommand(writer, "remove", packager.RemoveCommand) 140 fmt.Fprintln(writer, `[ "$cmd" = "run" ] && exec "$@"`) 141 multiplier := packager.ListCommand.SizeMultiplier 142 if multiplier < 1 { 143 multiplier = 1 144 } 145 fmt.Fprintf(writer, 146 "[ \"$cmd\" = \"show-size-multiplier\" ] && exec echo %d\n", multiplier) 147 writePackagerCommand(writer, "update", packager.UpdateCommand) 148 writePackagerCommand(writer, "upgrade", packager.UpgradeCommand) 149 fmt.Fprintln(writer, "echo \"Invalid command: $cmd\"") 150 fmt.Fprintln(writer, "exit 2") 151 } 152 153 func writePackagerCommand(writer io.Writer, cmd string, command []string) { 154 if len(command) < 1 { 155 fmt.Fprintf(writer, "[ \"$cmd\" = \"%s\" ] && exit 0\n", cmd) 156 } else { 157 fmt.Fprintf(writer, "[ \"$cmd\" = \"%s\" ] && exec", cmd) 158 for _, arg := range command { 159 writeArgument(writer, arg) 160 } 161 fmt.Fprintf(writer, " \"$@\"\n") 162 } 163 } 164 165 func writeArgument(writer io.Writer, arg string) { 166 if len(strings.Fields(arg)) < 2 { 167 fmt.Fprintf(writer, " %s", arg) 168 } else { 169 lenArg := len(arg) 170 if lenArg > 0 && arg[lenArg-1] == '\n' { 171 arg = arg[:lenArg-1] + `\n` 172 } 173 fmt.Fprintf(writer, " '%s'", arg) 174 } 175 } 176 177 func clearResolvConf(writer io.Writer, rootDir string) error { 178 return runInTarget(nil, writer, rootDir, nil, 179 "cp", "/dev/null", "/etc/resolv.conf") 180 } 181 182 func runInTarget(input io.Reader, output io.Writer, rootDir string, 183 envGetter environmentGetter, prog string, args ...string) error { 184 var environmentToInject map[string]string 185 if envGetter != nil { 186 environmentToInject = envGetter.getenv() 187 } 188 cmd := exec.Command(prog, args...) 189 cmd.Env = stripVariables(os.Environ(), environmentToCopy, environmentToSet, 190 environmentToInject) 191 cmd.Dir = "/" 192 cmd.Stdin = input 193 cmd.Stdout = output 194 cmd.Stderr = output 195 cmd.SysProcAttr = &syscall.SysProcAttr{ 196 Chroot: rootDir, 197 Setsid: true, 198 Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWPID, 199 } 200 return cmd.Run() 201 } 202 203 func runInTargetWithBindMounts(input io.Reader, output io.Writer, 204 rootDir string, bindMounts []string, envGetter environmentGetter, 205 prog string, args ...string) error { 206 if len(bindMounts) < 1 { 207 return runInTarget(input, output, rootDir, envGetter, prog, args...) 208 } 209 errChannel := make(chan error) 210 go func() { 211 err := func() error { 212 if err := wsyscall.UnshareMountNamespace(); err != nil { 213 return err 214 } 215 for _, bindMount := range bindMounts { 216 err := wsyscall.Mount(bindMount, 217 filepath.Join(rootDir, bindMount), "", 218 wsyscall.MS_BIND|wsyscall.MS_RDONLY, "") 219 if err != nil { 220 return fmt.Errorf("error bind mounting: %s: %s", 221 bindMount, err) 222 } 223 } 224 return runInTarget(input, output, rootDir, envGetter, prog, args...) 225 }() 226 errChannel <- err 227 }() 228 return <-errChannel 229 } 230 231 func stripVariables(input []string, varsToCopy map[string]struct{}, 232 varsToSet ...map[string]string) []string { 233 output := make([]string, 0) 234 for _, nameValue := range os.Environ() { 235 split := strings.SplitN(nameValue, "=", 2) 236 if len(split) == 2 { 237 if _, ok := varsToCopy[split[0]]; ok { 238 output = append(output, nameValue) 239 } 240 } 241 } 242 for _, varTable := range varsToSet { 243 for name, value := range varTable { 244 output = append(output, name+"="+value) 245 } 246 } 247 sort.Strings(output) 248 return output 249 }