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  }