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 }