github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imageunpacker/unpacker/exportImage.go (about) 1 package unpacker 2 3 import ( 4 "errors" 5 "flag" 6 "fmt" 7 "os" 8 "os/exec" 9 "os/user" 10 "path" 11 "strconv" 12 "syscall" 13 "time" 14 15 "github.com/Cloud-Foundations/Dominator/lib/format" 16 proto "github.com/Cloud-Foundations/Dominator/proto/imageunpacker" 17 ) 18 19 var ( 20 exportImageTool = flag.String("exportImageTool", 21 "/usr/local/etc/export-image", "Name of tool to export image") 22 exportImageUsername = flag.String("exportImageUsername", 23 "nobody", "Username to run as for export tool") 24 ) 25 26 func (u *Unpacker) exportImage(streamName string, 27 exportType string, exportDestination string) error { 28 u.rwMutex.Lock() 29 u.updateUsageTimeWithLock() 30 streamInfo, err := u.setupStream(streamName) 31 u.rwMutex.Unlock() 32 defer u.updateUsageTime() 33 if err != nil { 34 return err 35 } 36 errorChannel := make(chan error) 37 request := requestType{ 38 request: requestExport, 39 exportType: exportType, 40 exportDestination: exportDestination, 41 errorChannel: errorChannel, 42 } 43 streamInfo.requestChannel <- request 44 return <-errorChannel 45 } 46 47 func (stream *streamManagerState) export(exportType string, 48 exportDestination string) error { 49 userInfo, err := user.Lookup(*exportImageUsername) 50 if err != nil { 51 return err 52 } 53 groupIds, err := userInfo.GroupIds() 54 if err != nil { 55 return err 56 } 57 if err := stream.getDevice(); err != nil { 58 return err 59 } 60 stream.unpacker.rwMutex.RLock() 61 device := stream.unpacker.pState.Devices[stream.streamInfo.DeviceId] 62 stream.unpacker.rwMutex.RUnlock() 63 doUnmount := false 64 streamInfo := stream.streamInfo 65 switch streamInfo.status { 66 case proto.StatusStreamNoDevice: 67 return errors.New("no device") 68 case proto.StatusStreamNotMounted: 69 // Nothing to do. 70 case proto.StatusStreamMounted: 71 doUnmount = true 72 case proto.StatusStreamScanning: 73 return errors.New("stream scan in progress") 74 case proto.StatusStreamScanned: 75 doUnmount = true 76 case proto.StatusStreamFetching: 77 return errors.New("fetch in progress") 78 case proto.StatusStreamUpdating: 79 return errors.New("update in progress") 80 case proto.StatusStreamPreparing: 81 return errors.New("preparing to capture") 82 case proto.StatusStreamExporting: 83 return errors.New("export in progress") 84 case proto.StatusStreamNoFileSystem: 85 return errors.New("no file-system") 86 default: 87 panic("invalid status") 88 } 89 if doUnmount { 90 mountPoint := path.Join(stream.unpacker.baseDir, "mnt") 91 if err := syscall.Unmount(mountPoint, 0); err != nil { 92 return err 93 } 94 stream.streamInfo.status = proto.StatusStreamNotMounted 95 } 96 stream.streamInfo.status = proto.StatusStreamExporting 97 defer func() { 98 stream.streamInfo.status = proto.StatusStreamNotMounted 99 }() 100 deviceFile, err := os.Open(path.Join("/dev", device.DeviceName)) 101 if err != nil { 102 stream.unpacker.logger.Println("Error exporting: %s", err) 103 return fmt.Errorf("error exporting: %s", err) 104 } 105 defer deviceFile.Close() 106 cmd := exec.Command(*exportImageTool, exportType, exportDestination) 107 cmd.Stdin = deviceFile 108 uid, err := strconv.ParseUint(userInfo.Uid, 10, 32) 109 if err != nil { 110 return err 111 } 112 gid, err := strconv.ParseUint(userInfo.Gid, 10, 32) 113 if err != nil { 114 return err 115 } 116 gids := make([]uint32, 0, len(groupIds)) 117 for _, groupId := range groupIds { 118 gid, err := strconv.ParseUint(groupId, 10, 32) 119 if err != nil { 120 return err 121 } 122 gids = append(gids, uint32(gid)) 123 } 124 creds := &syscall.Credential{ 125 Uid: uint32(uid), 126 Gid: uint32(gid), 127 Groups: gids, 128 } 129 cmd.SysProcAttr = &syscall.SysProcAttr{Credential: creds} 130 startTime := time.Now() 131 output, err := cmd.CombinedOutput() 132 if err != nil { 133 stream.unpacker.logger.Printf("Error exporting: %s: %s\n", 134 err, string(output)) 135 return fmt.Errorf("error exporting: %s: %s", err, output) 136 } 137 stream.unpacker.logger.Printf("Exported(%s) type: %s dest: %s in %s\n", 138 stream.streamName, exportType, exportDestination, 139 format.Duration(time.Since(startTime))) 140 return nil 141 }