github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/upload_dirs.go (about) 1 package ddevapp 2 3 import ( 4 "fmt" 5 "os" 6 "path" 7 "strings" 8 9 "github.com/ddev/ddev/pkg/fileutil" 10 "github.com/ddev/ddev/pkg/util" 11 ) 12 13 // addUploadDir adds a new upload dir if it does not already exist in the list. 14 func (app *DdevApp) addUploadDir(uploadDir string) { 15 err := app.validateUploadDirs() 16 if err != nil { 17 util.Failed("Failed to validate upload_dirs: %v", err) 18 } 19 20 for _, existingUploadDir := range app.UploadDirs { 21 if uploadDir == existingUploadDir { 22 return 23 } 24 } 25 26 app.UploadDirs = append(app.UploadDirs, uploadDir) 27 } 28 29 // GetUploadDir returns the first upload (public files) directory. 30 // This value is relative to the docroot 31 func (app *DdevApp) GetUploadDir() string { 32 uploadDirs := app.GetUploadDirs() 33 if len(uploadDirs) > 0 { 34 return uploadDirs[0] 35 } 36 37 return "" 38 } 39 40 // GetUploadDirs returns the upload (public files) directories. 41 // These are gathered from the per-CMS configurations and the 42 // value of upload_dirs. upload_dirs overrides the per-CMS configuration 43 func (app *DdevApp) GetUploadDirs() []string { 44 err := app.validateUploadDirs() 45 if err != nil { 46 util.Warning("Ignoring invalid upload_dirs value: %v", err) 47 return []string{} 48 } 49 50 if app.UploadDirDeprecated != "" { 51 uploadDirDeprecated := app.UploadDirDeprecated 52 app.UploadDirDeprecated = "" 53 app.addUploadDir(uploadDirDeprecated) 54 } 55 56 // If an UploadDirs has been specified for the app, it overrides 57 // anything that the project type would give us. 58 if len(app.UploadDirs) > 0 { 59 return app.UploadDirs 60 } 61 62 // Otherwise continue to get the UploadDirs from the project type 63 appFuncs, ok := appTypeMatrix[app.GetType()] 64 if ok && appFuncs.uploadDirs != nil { 65 return appFuncs.uploadDirs(app) 66 } 67 68 return []string{} 69 } 70 71 // IsUploadDirsWarningDisabled returns true if UploadDirs is disabled by the user. 72 func (app *DdevApp) IsUploadDirsWarningDisabled() bool { 73 return app.DisableUploadDirsWarning 74 } 75 76 // calculateHostUploadDirFullPath returns the full path to the upload directory 77 // on the host or "" if there is none. 78 func (app *DdevApp) calculateHostUploadDirFullPath(uploadDir string) string { 79 if uploadDir != "" { 80 return path.Clean(path.Join(app.AppRoot, app.Docroot, uploadDir)) 81 } 82 83 return "" 84 } 85 86 // GetHostUploadDirFullPath returns the full path to the first upload directory on the 87 // host or "" if there is none. 88 func (app *DdevApp) GetHostUploadDirFullPath() string { 89 uploadDirs := app.GetUploadDirs() 90 if len(uploadDirs) > 0 { 91 return app.calculateHostUploadDirFullPath(uploadDirs[0]) 92 } 93 94 return "" 95 } 96 97 // calculateContainerUploadDirFullPath returns the full path to the upload 98 // directory in container or "" if there is none. 99 func (app *DdevApp) calculateContainerUploadDirFullPath(uploadDir string) string { 100 if uploadDir != "" { 101 return path.Clean(path.Join("/var/www/html", app.Docroot, uploadDir)) 102 } 103 104 return "" 105 } 106 107 // getContainerUploadDir returns the full path to the first upload 108 // directory in container or "" if there is none. 109 func (app *DdevApp) getContainerUploadDir() string { 110 uploadDirs := app.GetUploadDirs() 111 if len(uploadDirs) > 0 { 112 return app.calculateContainerUploadDirFullPath(uploadDirs[0]) 113 } 114 115 return "" 116 } 117 118 // getContainerUploadDirs returns a slice of the full path to the upload 119 // directories in container. 120 func (app *DdevApp) getContainerUploadDirs() []string { 121 uploadDirs := app.GetUploadDirs() 122 containerUploadDirs := make([]string, 0, len(uploadDirs)) 123 124 for _, uploadDir := range uploadDirs { 125 containerUploadDirs = append(containerUploadDirs, app.calculateContainerUploadDirFullPath(uploadDir)) 126 } 127 128 return containerUploadDirs 129 } 130 131 // getUploadDirsHostContainerMapping returns a slice containing host / container 132 // mapping separated by ":" to be used within docker-compose config. 133 func (app *DdevApp) getUploadDirsHostContainerMapping() []string { 134 uploadDirs := app.GetUploadDirs() 135 uploadDirsMapping := make([]string, 0, len(uploadDirs)) 136 137 for _, uploadDir := range uploadDirs { 138 hostUploadDir := app.calculateHostUploadDirFullPath(uploadDir) 139 140 // Exclude non existing dirs 141 if !fileutil.FileExists(hostUploadDir) { 142 continue 143 } 144 145 uploadDirsMapping = append(uploadDirsMapping, fmt.Sprintf( 146 "%s:%s", 147 hostUploadDir, 148 app.calculateContainerUploadDirFullPath(uploadDir), 149 )) 150 } 151 152 return uploadDirsMapping 153 } 154 155 // getUploadDirsRelative returns a slice containing upload dirs to be used with 156 // Mutagen config. 157 func (app *DdevApp) getUploadDirsRelative() []string { 158 uploadDirs := app.GetUploadDirs() 159 uploadDirsMap := make([]string, 0 /*, len(uploadDirs)*/) 160 161 for _, uploadDir := range uploadDirs { 162 hostUploadDir := app.calculateHostUploadDirFullPath(uploadDir) 163 164 // Exclude non existing dirs 165 if !fileutil.FileExists(hostUploadDir) { 166 continue 167 } 168 169 uploadDirsMap = append(uploadDirsMap, path.Join(app.Docroot, uploadDir)) 170 } 171 172 return uploadDirsMap 173 } 174 175 // CreateUploadDirsIfNecessary creates the upload dirs if it doesn't exist, so we can properly 176 // set up bind-mounts when doing Mutagen. 177 // There is no need to do it if Mutagen is not enabled, and 178 // we'll respect a symlink if it exists, and the user has to figure out the right 179 // thing to do with Mutagen. 180 func (app *DdevApp) CreateUploadDirsIfNecessary() { 181 for _, target := range app.GetUploadDirs() { 182 if hostDir := app.calculateHostUploadDirFullPath(target); hostDir != "" && app.IsMutagenEnabled() && !fileutil.FileExists(hostDir) { 183 err := os.MkdirAll(hostDir, 0755) 184 if err != nil { 185 util.Warning("Unable to create upload directory %s: %v", hostDir, err) 186 } 187 } 188 } 189 } 190 191 // validateUploadDirs validates and converts UploadDirs to app.UploadDirs 192 // interface or if disabled to bool false and returns nil if succeeded or an 193 // error if not. 194 // app.UploadDirs must be one of: 195 // - slice of string (possibly empty) 196 // - boolean false 197 func (app *DdevApp) validateUploadDirs() error { 198 199 // Check that upload dirs aren't outside the project root. 200 for _, uploadDir := range app.UploadDirs { 201 if !strings.HasPrefix(app.calculateHostUploadDirFullPath(uploadDir), app.AppRoot) { 202 return fmt.Errorf("invalid upload dir `%s` outside of project root `%s` found", uploadDir, app.AppRoot) 203 } 204 } 205 206 return nil 207 }