github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/hyper-control/makeInstallerIso.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "compress/gzip" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "sort" 13 "syscall" 14 15 imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client" 16 "github.com/Cloud-Foundations/Dominator/lib/errors" 17 "github.com/Cloud-Foundations/Dominator/lib/filesystem/util" 18 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 19 "github.com/Cloud-Foundations/Dominator/lib/log" 20 "github.com/Cloud-Foundations/Dominator/lib/log/nulllogger" 21 objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client" 22 "github.com/Cloud-Foundations/Dominator/lib/srpc" 23 fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager" 24 ) 25 26 const ( 27 dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 28 syscall.S_IROTH | syscall.S_IXOTH 29 ) 30 31 func makeInstallerIsoSubcommand(args []string, logger log.DebugLogger) error { 32 err := makeInstallerIso(args[0], args[1], logger) 33 if err != nil { 34 return fmt.Errorf("Error making installer ISO: %s", err) 35 } 36 return nil 37 } 38 39 func makeInstallerDirectory(hostname, rootDir string, logger log.DebugLogger) ( 40 *fm_proto.GetMachineInfoResponse, string, error) { 41 fmCR := srpc.NewClientResource("tcp", 42 fmt.Sprintf("%s:%d", *fleetManagerHostname, *fleetManagerPortNum)) 43 defer fmCR.ScheduleClose() 44 imageClient, err := srpc.DialHTTP("tcp", fmt.Sprintf("%s:%d", 45 *imageServerHostname, *imageServerPortNum), 0) 46 if err != nil { 47 return nil, "", err 48 } 49 defer imageClient.Close() 50 info, _, configFiles, err := getInstallConfig(fmCR, imageClient, hostname, 51 true, logger) 52 if err != nil { 53 return nil, "", err 54 } 55 err = unpackInstallerImage(rootDir, imageClient, nulllogger.New()) 56 if err != nil { 57 return nil, "", err 58 } 59 initrdFile := filepath.Join(rootDir, "initrd.img") 60 initrdRoot := filepath.Join(rootDir, "initrd.root") 61 if err := unpackInitrd(initrdRoot, initrdFile); err != nil { 62 return nil, "", err 63 } 64 configRoot := filepath.Join(initrdRoot, "tftpdata") 65 if err := writeConfigFiles(configRoot, configFiles); err != nil { 66 return nil, "", err 67 } 68 logger.Debugln(0, "building custom initrd with machine configuration") 69 if err := packInitrd(initrdFile, initrdRoot); err != nil { 70 return nil, "", err 71 } 72 return info, initrdFile, nil 73 } 74 75 func makeInstallerIso(hostname, dirname string, logger log.DebugLogger) error { 76 rootDir, err := ioutil.TempDir("", "iso") 77 if err != nil { 78 return err 79 } 80 defer os.RemoveAll(rootDir) 81 info, _, err := makeInstallerDirectory(hostname, rootDir, logger) 82 if err != nil { 83 return err 84 } 85 if info.Machine.IPMI.Hostname != "" { 86 hostname = info.Machine.IPMI.Hostname 87 } 88 filename := filepath.Join(dirname, hostname+".iso") 89 cmd := exec.Command("genisoimage", "-o", filename, "-b", "isolinux.bin", 90 "-c", "boot.catalogue", "-no-emul-boot", "-boot-load-size", "4", 91 "-boot-info-table", "-quiet", rootDir) 92 cmd.Stderr = os.Stderr 93 if err := cmd.Run(); err != nil { 94 return err 95 } 96 if len(info.Machine.IPMI.HostIpAddress) > 0 { 97 filename = filepath.Join(dirname, 98 info.Machine.IPMI.HostIpAddress.String()+".iso") 99 os.Remove(filename) 100 if err := os.Symlink(hostname+".iso", filename); err != nil { 101 return err 102 } 103 } 104 fmt.Println(filename) 105 return nil 106 } 107 108 func packInitrd(filename, rootDir string) error { 109 paths, err := walkTree(rootDir) 110 if err != nil { 111 return err 112 } 113 sort.Strings(paths) 114 file, err := os.Create(filename) 115 if err != nil { 116 return err 117 } 118 defer file.Close() 119 writer := gzip.NewWriter(file) 120 if err != nil { 121 return err 122 } 123 defer writer.Close() 124 // TODO(rgooch): Replace this with a library function using something like 125 // github.com/cavaliercoder/go-cpio. 126 cmd := exec.Command("cpio", "-o", "-H", "newc", "-R", "root.root", 127 "--quiet") 128 cmd.Dir = rootDir 129 cmd.Stdout = writer 130 cmd.Stderr = os.Stderr 131 cmdStdin, err := cmd.StdinPipe() 132 if err != nil { 133 return err 134 } 135 if err := cmd.Start(); err != nil { 136 return err 137 } 138 for _, path := range paths { 139 fmt.Fprintln(cmdStdin, path) 140 } 141 if err := cmdStdin.Close(); err != nil { 142 return err 143 } 144 if err := cmd.Wait(); err != nil { 145 return err 146 } 147 if err := os.RemoveAll(rootDir); err != nil { 148 return err 149 } 150 return nil 151 } 152 153 func unpackInitrd(rootDir, filename string) error { 154 if err := os.Mkdir(rootDir, dirPerms); err != nil { 155 return err 156 } 157 file, err := os.Open(filename) 158 if err != nil { 159 return err 160 } 161 defer file.Close() 162 reader, err := gzip.NewReader(bufio.NewReader(file)) 163 if err != nil { 164 return err 165 } 166 defer reader.Close() 167 // TODO(rgooch): Replace this with a library function using something like 168 // github.com/cavaliercoder/go-cpio. 169 cmd := exec.Command("cpio", "-i", "--make-directories", "--numeric-uid-gid", 170 "--preserve-modification-time", "--quiet") 171 cmd.Dir = rootDir 172 cmd.Stdin = reader 173 cmd.Stderr = os.Stderr 174 if err := cmd.Run(); err != nil { 175 return err 176 } 177 if err := os.Remove(filename); err != nil { 178 return err 179 } 180 return nil 181 } 182 183 func unpackInstallerImage(rootDir string, imageClient *srpc.Client, 184 logger log.DebugLogger) error { 185 imageName, err := imageclient.FindLatestImage(imageClient, 186 *installerImageStream, false) 187 if err != nil { 188 return err 189 } 190 if imageName == "" { 191 return errors.New("no image found") 192 } 193 image, err := imageclient.GetImage(imageClient, imageName) 194 if err != nil { 195 return err 196 } 197 if euid := uint32(os.Geteuid()); euid != 0 { 198 // Set the UID/GID to the user, otherwise unpacking will fail. This is a 199 // bit dirty. 200 // TODO(rgooch): Really want a util.UnpriviledgedUnpack() function. 201 egid := uint32(os.Getegid()) 202 image.FileSystem.SetGid(egid) 203 image.FileSystem.SetUid(euid) 204 for _, inode := range image.FileSystem.InodeTable { 205 inode.SetGid(egid) 206 inode.SetUid(euid) 207 } 208 } 209 image.FileSystem.RebuildInodePointers() 210 objClient := objectclient.AttachObjectClient(imageClient) 211 defer objClient.Close() 212 err = util.Unpack(image.FileSystem, objClient, rootDir, logger) 213 if err != nil { 214 return err 215 } 216 return nil 217 } 218 219 func walkTree(rootDir string) ([]string, error) { 220 rootLength := len(rootDir) 221 var paths []string 222 err := filepath.Walk(rootDir, 223 func(path string, info os.FileInfo, err error) error { 224 paths = append(paths, "."+path[rootLength:]) 225 return nil 226 }) 227 return paths, err 228 } 229 230 func writeConfigFiles(rootDir string, configFiles map[string][]byte) error { 231 if err := os.MkdirAll(rootDir, dirPerms); err != nil { 232 return err 233 } 234 for name, data := range configFiles { 235 err := fsutil.CopyToFile(filepath.Join(rootDir, name), filePerms, 236 bytes.NewReader(data), uint64(len(data))) 237 if err != nil { 238 return err 239 } 240 } 241 return nil 242 }