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  }