github.com/simonferquel/app@v0.6.1-0.20181012141724-68b7cccf26ac/internal/packager/extract.go (about) 1 package packager 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/url" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/docker/app/internal" 12 "github.com/docker/app/loader" 13 "github.com/docker/app/types" 14 "github.com/docker/distribution/reference" 15 "github.com/pkg/errors" 16 ) 17 18 // findApp looks for an app in CWD or subdirs 19 func findApp() (string, error) { 20 cwd, err := os.Getwd() 21 if err != nil { 22 return "", errors.Wrap(err, "cannot resolve current working directory") 23 } 24 if strings.HasSuffix(cwd, internal.AppExtension) { 25 return cwd, nil 26 } 27 content, err := ioutil.ReadDir(cwd) 28 if err != nil { 29 return "", errors.Wrap(err, "failed to read current working directory") 30 } 31 hit := "" 32 for _, c := range content { 33 if strings.HasSuffix(c.Name(), internal.AppExtension) { 34 if hit != "" { 35 return "", fmt.Errorf("multiple applications found in current directory, specify the application name on the command line") 36 } 37 hit = c.Name() 38 } 39 } 40 if hit == "" { 41 return "", fmt.Errorf("no application found in current directory") 42 } 43 return filepath.Join(cwd, hit), nil 44 } 45 46 func appNameFromRef(ref reference.Named) string { 47 parts := strings.Split(ref.Name(), "/") 48 return internal.DirNameFromAppName(parts[len(parts)-1]) 49 } 50 51 func imageNameFromRef(ref reference.Named) string { 52 if tagged, ok := ref.(reference.Tagged); ok { 53 name := internal.DirNameFromAppName(ref.Name()) 54 newRef, _ := reference.WithName(name) 55 newtaggedRef, _ := reference.WithTag(newRef, tagged.Tag()) 56 return newtaggedRef.String() 57 } 58 return internal.DirNameFromAppName(ref.String()) 59 } 60 61 // extractImage extracts a docker application in a docker image to a temporary directory 62 func extractImage(appname string, ops ...func(*types.App) error) (*types.App, error) { 63 ref, err := reference.ParseNormalizedNamed(appname) 64 if err != nil { 65 return nil, err 66 } 67 literalImageName := appname 68 imagename := imageNameFromRef(ref) 69 appname = appNameFromRef(ref) 70 tempDir, err := ioutil.TempDir("", "dockerapp") 71 if err != nil { 72 return nil, errors.Wrap(err, "failed to create temporary directory") 73 } 74 // Attempt loading image based on default name permutation 75 path, err := Pull(imagename, tempDir) 76 if err != nil { 77 if literalImageName == imagename { 78 os.RemoveAll(tempDir) 79 return nil, err 80 } 81 // Attempt loading image based on the literal name 82 path, err = Pull(literalImageName, tempDir) 83 if err != nil { 84 os.RemoveAll(tempDir) 85 return nil, err 86 } 87 } 88 ops = append(ops, types.WithName(appname), types.WithCleanup(func() { os.RemoveAll(tempDir) })) 89 return loader.LoadFromDirectory(path, ops...) 90 } 91 92 // Extract extracts the app content if argument is an archive, or does nothing if a dir. 93 // It returns source file, effective app name, and cleanup function 94 // If appname is empty, it looks into cwd, and all subdirs for a single matching .dockerapp 95 // If nothing is found, it looks for an image and loads it 96 func Extract(name string, ops ...func(*types.App) error) (*types.App, error) { 97 if name == "" { 98 var err error 99 if name, err = findApp(); err != nil { 100 return nil, err 101 } 102 } 103 if name == "." { 104 var err error 105 if name, err = os.Getwd(); err != nil { 106 return nil, errors.Wrap(err, "cannot resolve current working directory") 107 } 108 } 109 ops = append(ops, types.WithName(name)) 110 appname := internal.DirNameFromAppName(name) 111 s, err := os.Stat(appname) 112 if err != nil { 113 // URL or docker image 114 u, err := url.Parse(name) 115 if err == nil && (u.Scheme == "http" || u.Scheme == "https") { 116 ops = append(ops, types.WithSource(types.AppSourceURL)) 117 return loader.LoadFromURL(name, ops...) 118 } 119 // look for a docker image 120 ops = append(ops, types.WithSource(types.AppSourceImage)) 121 app, err := extractImage(name, ops...) 122 return app, errors.Wrapf(err, "cannot locate application %q in filesystem or registry", name) 123 } 124 if s.IsDir() { 125 // directory: already decompressed 126 appOpts := append(ops, 127 types.WithPath(appname), 128 types.WithSource(types.AppSourceSplit), 129 ) 130 return loader.LoadFromDirectory(appname, appOpts...) 131 } 132 // not a dir: single-file or a tarball package, extract that in a temp dir 133 app, err := loader.LoadFromTar(appname, ops...) 134 if err != nil { 135 f, err := os.Open(appname) 136 if err != nil { 137 return nil, err 138 } 139 defer f.Close() 140 ops = append(ops, types.WithSource(types.AppSourceMerged)) 141 return loader.LoadFromSingleFile(appname, f, ops...) 142 } 143 app.Source = types.AppSourceArchive 144 return app, nil 145 }