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  }