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 }