github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/actors/push.go (about) 1 package actors 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "runtime" 9 10 "code.cloudfoundry.org/cli/cf/api/applicationbits" 11 "code.cloudfoundry.org/cli/cf/api/resources" 12 "code.cloudfoundry.org/cli/cf/appfiles" 13 . "code.cloudfoundry.org/cli/cf/i18n" 14 "code.cloudfoundry.org/cli/cf/models" 15 "code.cloudfoundry.org/gofileutils/fileutils" 16 ) 17 18 const windowsPathPrefix = `\\?\` 19 20 //go:generate counterfeiter . PushActor 21 22 type PushActor interface { 23 UploadApp(appGUID string, zipFile *os.File, presentFiles []resources.AppFileResource) error 24 ProcessPath(dirOrZipFile string, f func(string) error) error 25 GatherFiles(localFiles []models.AppFileFields, appDir string, uploadDir string, useCache bool) ([]resources.AppFileResource, bool, error) 26 ValidateAppParams(apps []models.AppParams) []error 27 MapManifestRoute(routeName string, app models.Application, appParamsFromContext models.AppParams) error 28 } 29 30 type PushActorImpl struct { 31 appBitsRepo applicationbits.Repository 32 appfiles appfiles.AppFiles 33 zipper appfiles.Zipper 34 routeActor RouteActor 35 } 36 37 func NewPushActor(appBitsRepo applicationbits.Repository, zipper appfiles.Zipper, appfiles appfiles.AppFiles, routeActor RouteActor) PushActor { 38 return PushActorImpl{ 39 appBitsRepo: appBitsRepo, 40 appfiles: appfiles, 41 zipper: zipper, 42 routeActor: routeActor, 43 } 44 } 45 46 // ProcessPath takes in a director of app files or a zip file which contains 47 // the app files. If given a zip file, it will extract the zip to a temporary 48 // location, call the provided callback with that location, and then clean up 49 // the location after the callback has been executed. 50 // 51 // This was done so that the caller of ProcessPath wouldn't need to know if it 52 // was a zip file or an app dir that it was given, and the caller would not be 53 // responsible for cleaning up the temporary directory ProcessPath creates when 54 // given a zip. 55 func (actor PushActorImpl) ProcessPath(dirOrZipFile string, f func(string) error) error { 56 if !actor.zipper.IsZipFile(dirOrZipFile) { 57 if filepath.IsAbs(dirOrZipFile) { 58 appDir, err := filepath.EvalSymlinks(dirOrZipFile) 59 if err != nil { 60 return err 61 } 62 err = f(appDir) 63 if err != nil { 64 return err 65 } 66 } else { 67 absPath, err := filepath.Abs(dirOrZipFile) 68 if err != nil { 69 return err 70 } 71 appDir, err := filepath.EvalSymlinks(absPath) 72 if err != nil { 73 return err 74 } 75 76 err = f(appDir) 77 if err != nil { 78 return err 79 } 80 } 81 82 return nil 83 } 84 85 tempDir, err := ioutil.TempDir("", "unzipped-app") 86 if err != nil { 87 return err 88 } 89 90 err = actor.zipper.Unzip(dirOrZipFile, tempDir) 91 if err != nil { 92 return err 93 } 94 95 err = f(tempDir) 96 if err != nil { 97 return err 98 } 99 100 err = os.RemoveAll(tempDir) 101 if err != nil { 102 return err 103 } 104 105 return nil 106 } 107 108 func (actor PushActorImpl) GatherFiles(localFiles []models.AppFileFields, appDir string, uploadDir string, useCache bool) ([]resources.AppFileResource, bool, error) { 109 appFileResource := []resources.AppFileResource{} 110 for _, file := range localFiles { 111 appFileResource = append(appFileResource, resources.AppFileResource{ 112 Path: file.Path, 113 Sha1: file.Sha1, 114 Size: file.Size, 115 }) 116 } 117 118 var err error 119 // CC returns a list of files that it already has, so an empty list of 120 // remoteFiles is equivalent to not using resource caching at all 121 remoteFiles := []resources.AppFileResource{} 122 if useCache { 123 remoteFiles, err = actor.appBitsRepo.GetApplicationFiles(appFileResource) 124 if err != nil { 125 return []resources.AppFileResource{}, false, err 126 } 127 } 128 129 filesToUpload := make([]models.AppFileFields, len(localFiles), len(localFiles)) 130 copy(filesToUpload, localFiles) 131 132 for _, remoteFile := range remoteFiles { 133 for i, fileToUpload := range filesToUpload { 134 if remoteFile.Path == fileToUpload.Path { 135 filesToUpload = append(filesToUpload[:i], filesToUpload[i+1:]...) 136 } 137 } 138 } 139 140 err = actor.appfiles.CopyFiles(filesToUpload, appDir, uploadDir) 141 if err != nil { 142 return []resources.AppFileResource{}, false, err 143 } 144 145 _, err = os.Stat(filepath.Join(appDir, ".cfignore")) 146 if err == nil { 147 err = fileutils.CopyPathToPath(filepath.Join(appDir, ".cfignore"), filepath.Join(uploadDir, ".cfignore")) 148 if err != nil { 149 return []resources.AppFileResource{}, false, err 150 } 151 } 152 153 for i := range remoteFiles { 154 fullPath, err := filepath.Abs(filepath.Join(appDir, remoteFiles[i].Path)) 155 if err != nil { 156 return []resources.AppFileResource{}, false, err 157 } 158 159 if runtime.GOOS == "windows" { 160 fullPath = windowsPathPrefix + fullPath 161 } 162 fileInfo, err := os.Lstat(fullPath) 163 if err != nil { 164 return []resources.AppFileResource{}, false, err 165 } 166 fileMode := fileInfo.Mode() 167 168 if runtime.GOOS == "windows" { 169 fileMode = fileMode | 0700 170 } 171 172 remoteFiles[i].Mode = fmt.Sprintf("%#o", fileMode) 173 } 174 175 return remoteFiles, len(filesToUpload) > 0, nil 176 } 177 178 func (actor PushActorImpl) UploadApp(appGUID string, zipFile *os.File, presentFiles []resources.AppFileResource) error { 179 return actor.appBitsRepo.UploadBits(appGUID, zipFile, presentFiles) 180 } 181 182 func (actor PushActorImpl) ValidateAppParams(apps []models.AppParams) []error { 183 errs := []error{} 184 185 for _, app := range apps { 186 appName := app.Name 187 188 if app.HealthCheckType != nil && *app.HealthCheckType != "http" && app.HealthCheckHTTPEndpoint != nil { 189 errs = append(errs, fmt.Errorf(T("Health check type must be 'http' to set a health check HTTP endpoint."))) 190 } 191 192 if app.Routes != nil { 193 if app.Hosts != nil { 194 errs = append(errs, fmt.Errorf(T("Application {{.AppName}} must not be configured with both 'routes' and 'host'/'hosts'", map[string]interface{}{"AppName": appName}))) 195 } 196 197 if app.Domains != nil { 198 errs = append(errs, fmt.Errorf(T("Application {{.AppName}} must not be configured with both 'routes' and 'domain'/'domains'", map[string]interface{}{"AppName": appName}))) 199 } 200 201 if app.NoHostname != nil { 202 errs = append(errs, fmt.Errorf(T("Application {{.AppName}} must not be configured with both 'routes' and 'no-hostname'", map[string]interface{}{"AppName": appName}))) 203 } 204 } 205 206 if app.BuildpackURL != nil && app.DockerImage != nil { 207 errs = append(errs, fmt.Errorf(T("Application {{.AppName}} must not be configured with both 'buildpack' and 'docker'", map[string]interface{}{"AppName": appName}))) 208 } 209 210 if app.Path != nil && app.DockerImage != nil { 211 errs = append(errs, fmt.Errorf(T("Application {{.AppName}} must not be configured with both 'docker' and 'path'", map[string]interface{}{"AppName": appName}))) 212 } 213 } 214 215 if len(errs) > 0 { 216 return errs 217 } 218 219 return nil 220 } 221 222 func (actor PushActorImpl) MapManifestRoute(routeName string, app models.Application, appParamsFromContext models.AppParams) error { 223 return actor.routeActor.FindAndBindRoute(routeName, app, appParamsFromContext) 224 }