github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/export.go (about)

     1  // Copyright 2016 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"archive/tar"
    19  	"compress/gzip"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"syscall"
    26  
    27  	"github.com/appc/spec/aci"
    28  	"github.com/appc/spec/schema"
    29  	"github.com/appc/spec/schema/types"
    30  	"github.com/hashicorp/errwrap"
    31  	"github.com/rkt/rkt/common"
    32  	"github.com/rkt/rkt/common/overlay"
    33  	"github.com/rkt/rkt/pkg/mountinfo"
    34  	pkgPod "github.com/rkt/rkt/pkg/pod"
    35  	"github.com/rkt/rkt/pkg/user"
    36  	"github.com/rkt/rkt/store/imagestore"
    37  	"github.com/rkt/rkt/store/treestore"
    38  	"github.com/spf13/cobra"
    39  )
    40  
    41  var (
    42  	cmdExport = &cobra.Command{
    43  		Use:   "export [--app=APPNAME] UUID OUTPUT_ACI_FILE",
    44  		Short: "Export an app from an exited pod to an ACI file",
    45  		Long:  `UUID should be the uuid of an exited pod.`,
    46  		Run:   runWrapper(runExport),
    47  	}
    48  	flagExportAppName string
    49  )
    50  
    51  func init() {
    52  	cmdRkt.AddCommand(cmdExport)
    53  	cmdExport.Flags().StringVar(&flagExportAppName, "app", "", "name of the app to export within the specified pod")
    54  	cmdExport.Flags().BoolVar(&flagOverwriteACI, "overwrite", false, "overwrite output ACI")
    55  }
    56  
    57  func runExport(cmd *cobra.Command, args []string) (exit int) {
    58  	if len(args) != 2 {
    59  		cmd.Usage()
    60  		return 254
    61  	}
    62  
    63  	outACI := args[1]
    64  	ext := filepath.Ext(outACI)
    65  	if ext != schema.ACIExtension {
    66  		stderr.Printf("extension must be %s (given %s)", schema.ACIExtension, outACI)
    67  		return 254
    68  	}
    69  
    70  	p, err := pkgPod.PodFromUUIDString(getDataDir(), args[0])
    71  	if err != nil {
    72  		stderr.PrintE("problem retrieving pod", err)
    73  		return 254
    74  	}
    75  	defer p.Close()
    76  
    77  	state := p.State()
    78  	if state != pkgPod.Exited && state != pkgPod.ExitedGarbage {
    79  		stderr.Print("pod is not exited. Only exited pods can be exported")
    80  		return 254
    81  	}
    82  
    83  	app, err := getApp(p)
    84  	if err != nil {
    85  		stderr.PrintE("unable to find app", err)
    86  		return 254
    87  	}
    88  
    89  	root := common.AppPath(p.Path(), app.Name)
    90  	manifestPath := filepath.Join(common.AppInfoPath(p.Path(), app.Name), aci.ManifestFile)
    91  	if p.UsesOverlay() {
    92  		tmpDir := filepath.Join(getDataDir(), "tmp")
    93  		if err := os.MkdirAll(tmpDir, common.DefaultRegularDirPerm); err != nil {
    94  			stderr.PrintE("unable to create temp directory", err)
    95  			return 254
    96  		}
    97  		podDir, err := ioutil.TempDir(tmpDir, fmt.Sprintf("rkt-export-%s", p.UUID))
    98  		if err != nil {
    99  			stderr.PrintE("unable to create export temp directory", err)
   100  			return 254
   101  		}
   102  		defer func() {
   103  			if err := os.RemoveAll(podDir); err != nil {
   104  				stderr.PrintE("problem removing temp directory", err)
   105  				exit = 1
   106  			}
   107  		}()
   108  		mntDir := filepath.Join(podDir, "rootfs")
   109  		if err := os.Mkdir(mntDir, common.DefaultRegularDirPerm); err != nil {
   110  			stderr.PrintE("unable to create rootfs directory inside temp directory", err)
   111  			return 254
   112  		}
   113  
   114  		if err := mountOverlay(p, app, mntDir); err != nil {
   115  			stderr.PrintE(fmt.Sprintf("couldn't mount directory at %s", mntDir), err)
   116  			return 254
   117  		}
   118  		defer func() {
   119  			if err := syscall.Unmount(mntDir, 0); err != nil {
   120  				stderr.PrintE(fmt.Sprintf("error unmounting directory %s", mntDir), err)
   121  				exit = 1
   122  			}
   123  		}()
   124  		root = podDir
   125  	} else {
   126  		// trailing filepath separator so we don't match the appRootfs path
   127  		appRootfs := common.AppRootfsPath(p.Path(), app.Name) + string(filepath.Separator)
   128  		mnts, err := mountinfo.ParseMounts(0)
   129  		if err != nil {
   130  			stderr.PrintE("error parsing mountpoints", err)
   131  			return 254
   132  		}
   133  		mnts = mnts.Filter(mountinfo.HasPrefix(appRootfs))
   134  		if len(mnts) > 0 {
   135  			stderr.Printf("pod has remaining mountpoints. Only pods using overlayfs or with no mountpoints can be exported")
   136  			return 254
   137  		}
   138  	}
   139  
   140  	// Check for user namespace (--private-user), if in use get uidRange
   141  	var uidRange *user.UidRange
   142  	privUserFile := filepath.Join(p.Path(), common.PrivateUsersPreparedFilename)
   143  	privUserContent, err := ioutil.ReadFile(privUserFile)
   144  	if err == nil {
   145  		uidRange = user.NewBlankUidRange()
   146  		// The file was found, save uid & gid shift and count
   147  		if err := uidRange.Deserialize(privUserContent); err != nil {
   148  			stderr.PrintE(fmt.Sprintf("problem deserializing the content of %s", common.PrivateUsersPreparedFilename), err)
   149  			return 254
   150  		}
   151  	}
   152  
   153  	if err = buildAci(root, manifestPath, outACI, uidRange); err != nil {
   154  		stderr.PrintE("error building aci", err)
   155  		return 254
   156  	}
   157  	return 0
   158  }
   159  
   160  // getApp returns the app to export
   161  // If one was supplied in the flags then it's returned if present
   162  // If the PM contains a single app, that app is returned
   163  // If the PM has multiple apps, the names are printed and an error is returned
   164  func getApp(p *pkgPod.Pod) (*schema.RuntimeApp, error) {
   165  	_, manifest, err := p.PodManifest()
   166  	if err != nil {
   167  		return nil, errwrap.Wrap(errors.New("problem getting the pod's manifest"), err)
   168  	}
   169  
   170  	apps := manifest.Apps
   171  
   172  	if flagExportAppName != "" {
   173  		exportAppName, err := types.NewACName(flagExportAppName)
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  		for _, ra := range apps {
   178  			if *exportAppName == ra.Name {
   179  				return &ra, nil
   180  			}
   181  		}
   182  		return nil, fmt.Errorf("app %s is not present in pod", flagExportAppName)
   183  	}
   184  
   185  	switch len(apps) {
   186  	case 0:
   187  		return nil, fmt.Errorf("pod contains zero apps")
   188  	case 1:
   189  		return &apps[0], nil
   190  	default:
   191  	}
   192  
   193  	stderr.Print("pod contains multiple apps:")
   194  	for _, ra := range apps {
   195  		stderr.Printf("\t%v", ra.Name)
   196  	}
   197  
   198  	return nil, fmt.Errorf("specify app using \"rkt export --app= ...\"")
   199  }
   200  
   201  // mountOverlay mounts the app from the overlay-rendered pod to the destination directory.
   202  func mountOverlay(pod *pkgPod.Pod, app *schema.RuntimeApp, dest string) error {
   203  	if _, err := os.Stat(dest); err != nil {
   204  		return err
   205  	}
   206  
   207  	s, err := imagestore.NewStore(getDataDir())
   208  	if err != nil {
   209  		return errwrap.Wrap(errors.New("cannot open store"), err)
   210  	}
   211  
   212  	ts, err := treestore.NewStore(treeStoreDir(), s)
   213  	if err != nil {
   214  		return errwrap.Wrap(errors.New("cannot open treestore"), err)
   215  	}
   216  
   217  	treeStoreID, err := pod.GetAppTreeStoreID(app.Name)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	lower := ts.GetRootFS(treeStoreID)
   222  	imgDir := filepath.Join(filepath.Join(pod.Path(), "overlay"), treeStoreID)
   223  	if _, err := os.Stat(imgDir); err != nil {
   224  		return err
   225  	}
   226  	upper := filepath.Join(imgDir, "upper", app.Name.String())
   227  	if _, err := os.Stat(upper); err != nil {
   228  		return err
   229  	}
   230  	work := filepath.Join(imgDir, "work", app.Name.String())
   231  	if _, err := os.Stat(work); err != nil {
   232  		return err
   233  	}
   234  
   235  	if err := overlay.Mount(&overlay.MountCfg{lower, upper, work, dest, ""}); err != nil {
   236  		return errwrap.Wrap(errors.New("problem mounting overlayfs directory"), err)
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // buildAci builds a target aci from the root directory using any uid shift
   243  // information from uidRange.
   244  func buildAci(root, manifestPath, target string, uidRange *user.UidRange) (e error) {
   245  	mode := os.O_CREATE | os.O_WRONLY
   246  	if flagOverwriteACI {
   247  		mode |= os.O_TRUNC
   248  	} else {
   249  		mode |= os.O_EXCL
   250  	}
   251  	aciFile, err := os.OpenFile(target, mode, 0644)
   252  	if err != nil {
   253  		if os.IsExist(err) {
   254  			return errors.New("target file exists (try --overwrite)")
   255  		} else {
   256  			return errwrap.Wrap(fmt.Errorf("unable to open target %s", target), err)
   257  		}
   258  	}
   259  
   260  	gw := gzip.NewWriter(aciFile)
   261  	tr := tar.NewWriter(gw)
   262  
   263  	defer func() {
   264  		tr.Close()
   265  		gw.Close()
   266  		aciFile.Close()
   267  		// e is implicitly assigned by the return statement. As defer runs
   268  		// after return, but before actually returning, this works.
   269  		if e != nil {
   270  			os.Remove(target)
   271  		}
   272  	}()
   273  
   274  	b, err := ioutil.ReadFile(manifestPath)
   275  	if err != nil {
   276  		return errwrap.Wrap(errors.New("unable to read Image Manifest"), err)
   277  	}
   278  	var im schema.ImageManifest
   279  	if err := im.UnmarshalJSON(b); err != nil {
   280  		return errwrap.Wrap(errors.New("unable to load Image Manifest"), err)
   281  	}
   282  	iw := aci.NewImageWriter(im, tr)
   283  
   284  	// Unshift uid and gid when pod was started with --private-user (user namespace)
   285  	var walkerCb aci.TarHeaderWalkFunc = func(hdr *tar.Header) bool {
   286  		if uidRange != nil {
   287  			uid, gid, err := uidRange.UnshiftRange(uint32(hdr.Uid), uint32(hdr.Gid))
   288  			if err != nil {
   289  				stderr.PrintE("error unshifting gid and uid", err)
   290  				return false
   291  			}
   292  			hdr.Uid, hdr.Gid = int(uid), int(gid)
   293  		}
   294  		return true
   295  	}
   296  
   297  	if err := filepath.Walk(root, aci.BuildWalker(root, iw, walkerCb)); err != nil {
   298  		return errwrap.Wrap(errors.New("error walking rootfs"), err)
   299  	}
   300  
   301  	if err = iw.Close(); err != nil {
   302  		return errwrap.Wrap(fmt.Errorf("unable to close image %s", target), err)
   303  	}
   304  
   305  	return
   306  }