github.com/simonferquel/app@v0.6.1-0.20181012141724-68b7cccf26ac/internal/packager/registry.go (about)

     1  package packager
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/docker/app/internal"
    11  	"github.com/docker/app/pkg/resto"
    12  	"github.com/docker/app/types"
    13  	"github.com/docker/distribution/reference"
    14  	"github.com/pkg/errors"
    15  	log "github.com/sirupsen/logrus"
    16  )
    17  
    18  type imageComponents struct {
    19  	Name       string
    20  	Repository string
    21  	Tag        string
    22  }
    23  
    24  func splitImageName(repotag string) (*imageComponents, error) {
    25  	named, err := reference.ParseNormalizedNamed(repotag)
    26  	if err != nil {
    27  		return nil, errors.Wrap(err, "failed to parse image name")
    28  	}
    29  	res := &imageComponents{
    30  		Repository: named.Name(),
    31  	}
    32  	res.Name = res.Repository[strings.LastIndex(res.Repository, "/")+1:]
    33  	if tagged, ok := named.(reference.Tagged); ok {
    34  		res.Tag = tagged.Tag()
    35  	}
    36  	return res, nil
    37  }
    38  
    39  // Pull loads an app from a registry and returns the extracted dir name
    40  func Pull(repotag string, outputDir string) (string, error) {
    41  	imgRef, err := splitImageName(repotag)
    42  	if err != nil {
    43  		return "", errors.Wrapf(err, "origin %q is not a valid image name", repotag)
    44  	}
    45  	payload, err := resto.PullConfigMulti(context.Background(), repotag, resto.RegistryOptions{})
    46  	if err != nil {
    47  		return "", err
    48  	}
    49  	appDir := filepath.Join(outputDir, internal.DirNameFromAppName(imgRef.Name))
    50  	if err := os.Mkdir(appDir, 0755); err != nil {
    51  		return "", errors.Wrap(err, "failed to create output application directory")
    52  	}
    53  	if err := ExtractImagePayloadToDiskFiles(appDir, payload); err != nil {
    54  		return "", err
    55  	}
    56  	return appDir, nil
    57  }
    58  
    59  // ExtractImagePayloadToDiskFiles extracts all the files out of the image payload and onto disk
    60  // creating all necessary folders in between.
    61  func ExtractImagePayloadToDiskFiles(appDir string, payload map[string]string) error {
    62  	for localFilepath, filedata := range payload {
    63  		fileBytes := []byte(filedata)
    64  		// Deal with windows/linux slashes
    65  		convertedFilepath := filepath.FromSlash(localFilepath)
    66  
    67  		// Check we aren't doing ./../../../ etc in the path
    68  		fullFilepath := filepath.Join(appDir, convertedFilepath)
    69  		if _, err := filepath.Rel(appDir, fullFilepath); err != nil {
    70  			log.Warnf("dropping image entry %q with unexpected path outside of app dir", localFilepath)
    71  			continue
    72  		}
    73  
    74  		// Create the directories for any nested files
    75  		basepath := filepath.Dir(fullFilepath)
    76  		if err := os.MkdirAll(basepath, os.ModePerm); err != nil {
    77  			return errors.Wrapf(err, "failed to create directories for file: %s", fullFilepath)
    78  		}
    79  		if err := ioutil.WriteFile(fullFilepath, fileBytes, 0644); err != nil {
    80  			return errors.Wrapf(err, "failed to write output file: %s", fullFilepath)
    81  		}
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  // Push pushes an app to a registry. Returns the image digest.
    88  func Push(app *types.App, namespace, tag, repo string) (string, error) {
    89  	payload, err := createPayload(app)
    90  	if err != nil {
    91  		return "", errors.Wrap(err, "failed to read external file while creating payload for push")
    92  	}
    93  	imageName := createImageName(app, namespace, tag, repo)
    94  	return resto.PushConfigMulti(context.Background(), payload, imageName, resto.RegistryOptions{}, nil)
    95  }
    96  
    97  func createImageName(app *types.App, namespace, tag, repo string) string {
    98  	if namespace == "" || tag == "" {
    99  		metadata := app.Metadata()
   100  		if namespace == "" {
   101  			namespace = metadata.Namespace
   102  		}
   103  		if tag == "" {
   104  			tag = metadata.Version
   105  		}
   106  	}
   107  	if repo == "" {
   108  		repo = internal.AppNameFromDir(app.Name) + internal.AppExtension
   109  	}
   110  	if namespace != "" && namespace[len(namespace)-1] != '/' {
   111  		namespace += "/"
   112  	}
   113  	return namespace + repo + ":" + tag
   114  }
   115  
   116  func createPayload(app *types.App) (map[string]string, error) {
   117  	payload := map[string]string{
   118  		internal.MetadataFileName: string(app.MetadataRaw()),
   119  		internal.ComposeFileName:  string(app.Composes()[0]),
   120  		internal.SettingsFileName: string(app.SettingsRaw()[0]),
   121  	}
   122  	if err := readAttachments(payload, app.Path, app.Attachments()); err != nil {
   123  		return nil, err
   124  	}
   125  	return payload, nil
   126  }
   127  
   128  func readAttachments(payload map[string]string, parentDirPath string, files []types.Attachment) error {
   129  	var errs []string
   130  	for _, file := range files {
   131  		// Convert to local OS filepath slash syntax
   132  		fullFilePath := filepath.Join(parentDirPath, filepath.FromSlash(file.Path()))
   133  		filedata, err := ioutil.ReadFile(fullFilePath)
   134  		if err != nil {
   135  			errs = append(errs, err.Error())
   136  			continue
   137  		}
   138  		payload[file.Path()] = string(filedata)
   139  	}
   140  	return newErrGroup(errs)
   141  }
   142  
   143  func newErrGroup(errs []string) error {
   144  	if len(errs) == 0 {
   145  		return nil
   146  	}
   147  	return errors.New(strings.Join(errs, "\n"))
   148  }