github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/imagetool/addImageCommon.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"bufio"
     6  	"compress/gzip"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/Cloud-Foundations/Dominator/imageserver/client"
    17  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    18  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner"
    19  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/untar"
    20  	"github.com/Cloud-Foundations/Dominator/lib/filter"
    21  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    22  	"github.com/Cloud-Foundations/Dominator/lib/image"
    23  	"github.com/Cloud-Foundations/Dominator/lib/mbr"
    24  	objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client"
    25  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    26  	"github.com/Cloud-Foundations/Dominator/lib/srpc/setupclient"
    27  	"github.com/Cloud-Foundations/Dominator/lib/triggers"
    28  	"github.com/Cloud-Foundations/Dominator/lib/wsyscall"
    29  )
    30  
    31  type hasher struct {
    32  	objQ *objectclient.ObjectAdderQueue
    33  }
    34  
    35  func addImage(imageSClient *srpc.Client, name string, img *image.Image) error {
    36  	if *expiresIn > 0 {
    37  		img.ExpiresAt = time.Now().Add(*expiresIn)
    38  	} else {
    39  		img.ExpiresAt = time.Time{}
    40  	}
    41  	if err := img.Verify(); err != nil {
    42  		return err
    43  	}
    44  	if err := img.VerifyRequiredPaths(requiredPaths); err != nil {
    45  		return err
    46  	}
    47  	if err := client.AddImage(imageSClient, name, img); err != nil {
    48  		return errors.New("remote error: " + err.Error())
    49  	}
    50  	return nil
    51  }
    52  
    53  func (h *hasher) Hash(reader io.Reader, length uint64) (
    54  	hash.Hash, error) {
    55  	hash, err := h.objQ.Add(reader, length)
    56  	if err != nil {
    57  		return hash, errors.New("error sending image data: " + err.Error())
    58  	}
    59  	return hash, nil
    60  }
    61  
    62  func buildImage(imageSClient *srpc.Client, filter *filter.Filter,
    63  	imageFilename string) (*filesystem.FileSystem, error) {
    64  	var h hasher
    65  	var err error
    66  	h.objQ, err = objectclient.NewObjectAdderQueue(imageSClient)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	fs, err := buildImageWithHasher(imageSClient, filter, imageFilename, &h)
    71  	if err != nil {
    72  		h.objQ.Close()
    73  		return nil, err
    74  	}
    75  	err = h.objQ.Close()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	return fs, nil
    80  }
    81  
    82  func buildImageWithHasher(imageSClient *srpc.Client, filter *filter.Filter,
    83  	imageFilename string, h scanner.Hasher) (*filesystem.FileSystem, error) {
    84  	fi, err := os.Lstat(imageFilename)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	if fi.IsDir() {
    89  		sfs, err := scanner.ScanFileSystem(imageFilename, nil, filter, nil, h,
    90  			nil)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  		return &sfs.FileSystem, nil
    95  	}
    96  	imageFile, err := os.Open(imageFilename)
    97  	if err != nil {
    98  		return nil, errors.New("error opening image file: " + err.Error())
    99  	}
   100  	defer imageFile.Close()
   101  	if partitionTable, err := mbr.Decode(imageFile); err != nil {
   102  		if err != io.EOF {
   103  			return nil, err
   104  		} // Else perhaps a tiny tarfile, definitely not a partition table.
   105  	} else if partitionTable != nil {
   106  		return buildImageFromRaw(imageSClient, filter, imageFile,
   107  			partitionTable, h)
   108  	}
   109  	var imageReader io.Reader
   110  	if strings.HasSuffix(imageFilename, ".tar") {
   111  		imageReader = imageFile
   112  	} else if strings.HasSuffix(imageFilename, ".tar.gz") ||
   113  		strings.HasSuffix(imageFilename, ".tgz") {
   114  		gzipReader, err := gzip.NewReader(imageFile)
   115  		if err != nil {
   116  			return nil, errors.New(
   117  				"error creating gzip reader: " + err.Error())
   118  		}
   119  		defer gzipReader.Close()
   120  		imageReader = gzipReader
   121  	} else {
   122  		return nil, errors.New("unrecognised image type")
   123  	}
   124  	tarReader := tar.NewReader(imageReader)
   125  	fs, err := untar.Decode(tarReader, h, filter)
   126  	if err != nil {
   127  		return nil, errors.New("error building image: " + err.Error())
   128  	}
   129  	return fs, nil
   130  }
   131  
   132  func buildImageFromRaw(imageSClient *srpc.Client, filter *filter.Filter,
   133  	imageFile *os.File, partitionTable *mbr.Mbr,
   134  	h scanner.Hasher) (*filesystem.FileSystem, error) {
   135  	var index uint
   136  	var offsetOfLargest, sizeOfLargest uint64
   137  	numPartitions := partitionTable.GetNumPartitions()
   138  	for index = 0; index < numPartitions; index++ {
   139  		offset := partitionTable.GetPartitionOffset(index)
   140  		size := partitionTable.GetPartitionSize(index)
   141  		if size > sizeOfLargest {
   142  			offsetOfLargest = offset
   143  			sizeOfLargest = size
   144  		}
   145  	}
   146  	if sizeOfLargest < 1 {
   147  		return nil, errors.New("unable to find largest partition")
   148  	}
   149  	if err := wsyscall.UnshareMountNamespace(); err != nil {
   150  		if os.IsPermission(err) {
   151  			// Try again with sudo(8).
   152  			args := make([]string, 0, len(os.Args)+1)
   153  			if sudoPath, err := exec.LookPath("sudo"); err != nil {
   154  				return nil, err
   155  			} else {
   156  				args = append(args, sudoPath)
   157  			}
   158  			if myPath, err := exec.LookPath(os.Args[0]); err != nil {
   159  				return nil, err
   160  			} else {
   161  				args = append(args, myPath)
   162  			}
   163  			args = append(args, fmt.Sprintf("-certDirectory=%s",
   164  				setupclient.GetCertDirectory()))
   165  			args = append(args, os.Args[1:]...)
   166  			if err := syscall.Exec(args[0], args, os.Environ()); err != nil {
   167  				return nil, errors.New("unable to Exec: " + err.Error())
   168  			}
   169  		}
   170  		return nil, errors.New(
   171  			"error unsharing mount namespace: " + err.Error())
   172  	}
   173  	cmd := exec.Command("mount", "-o",
   174  		fmt.Sprintf("loop,offset=%d", offsetOfLargest), imageFile.Name(),
   175  		"/mnt")
   176  	cmd.Stdin = os.Stdin
   177  	cmd.Stdout = os.Stdout
   178  	cmd.Stderr = os.Stderr
   179  	if err := cmd.Run(); err != nil {
   180  		return nil, err
   181  	}
   182  	fs, err := buildImageWithHasher(imageSClient, filter, "/mnt", h)
   183  	syscall.Unmount("/mnt", 0)
   184  	return fs, err
   185  }
   186  
   187  func loadImageFiles(image *image.Image, objectClient *objectclient.ObjectClient,
   188  	filterFilename, triggersFilename string) error {
   189  	var err error
   190  	if filterFilename != "" {
   191  		image.Filter, err = filter.Load(filterFilename)
   192  		if err != nil {
   193  			return err
   194  		}
   195  	}
   196  	if triggersFilename != "" {
   197  		image.Triggers, err = triggers.Load(triggersFilename)
   198  		if err != nil {
   199  			return err
   200  		}
   201  	}
   202  	image.BuildLog, err = getAnnotation(objectClient, *buildLog)
   203  	if err != nil {
   204  		return err
   205  	}
   206  	image.ReleaseNotes, err = getAnnotation(objectClient, *releaseNotes)
   207  	if err != nil {
   208  		return err
   209  	}
   210  	return nil
   211  }
   212  
   213  func getAnnotation(objectClient *objectclient.ObjectClient, name string) (
   214  	*image.Annotation, error) {
   215  	if name == "" {
   216  		return nil, nil
   217  	}
   218  	file, err := os.Open(name)
   219  	if err != nil {
   220  		return &image.Annotation{URL: name}, nil
   221  	}
   222  	defer file.Close()
   223  	fi, err := file.Stat()
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	reader := bufio.NewReader(file)
   228  	hash, _, err := objectClient.AddObject(reader, uint64(fi.Size()), nil)
   229  	return &image.Annotation{Object: &hash}, err
   230  }