github.com/drud/ddev@v1.21.5-alpha1.0.20230226034409-94fcc4b94453/pkg/ddevapp/apptypes.go (about) 1 package ddevapp 2 3 import ( 4 "fmt" 5 "github.com/drud/ddev/pkg/nodeps" 6 "github.com/drud/ddev/pkg/util" 7 "os" 8 "path" 9 "path/filepath" 10 "sort" 11 ) 12 13 type settingsCreator func(*DdevApp) (string, error) 14 type uploadDir func(*DdevApp) string 15 16 // hookDefaultComments should probably change its arg from string to app when 17 // config refactor is done. 18 type hookDefaultComments func() []byte 19 20 type apptypeSettingsPaths func(app *DdevApp) 21 22 // appTypeDetect returns true if the app is of the specified type 23 type appTypeDetect func(app *DdevApp) bool 24 25 // postImportDBAction can take actions after import (like warning user about 26 // required actions on Wordpress. 27 type postImportDBAction func(app *DdevApp) error 28 29 // configOverrideAction allows a particular apptype to override elements 30 // of the config for that apptype. Key example is drupal6 needing php56 31 type configOverrideAction func(app *DdevApp) error 32 33 // postConfigAction allows actions to take place at the end of ddev config 34 type postConfigAction func(app *DdevApp) error 35 36 // postStartAction allows actions to take place at the end of ddev start 37 type postStartAction func(app *DdevApp) error 38 39 // importFilesAction 40 type importFilesAction func(app *DdevApp, importPath, extPath string) error 41 42 // defaultWorkingDirMap returns the app type's default working directory map 43 type defaultWorkingDirMap func(app *DdevApp, defaults map[string]string) map[string]string 44 45 // AppTypeFuncs struct defines the functions that can be called (if populated) 46 // for a given appType. 47 type AppTypeFuncs struct { 48 settingsCreator 49 uploadDir 50 hookDefaultComments 51 apptypeSettingsPaths 52 appTypeDetect 53 postImportDBAction 54 configOverrideAction 55 postConfigAction 56 postStartAction 57 importFilesAction 58 defaultWorkingDirMap 59 } 60 61 // appTypeMatrix is a static map that defines the various functions to be called 62 // for each apptype (CMS). 63 var appTypeMatrix map[string]AppTypeFuncs 64 65 func init() { 66 appTypeMatrix = map[string]AppTypeFuncs{ 67 nodeps.AppTypeBackdrop: { 68 settingsCreator: createBackdropSettingsFile, uploadDir: getBackdropUploadDir, hookDefaultComments: getBackdropHooks, apptypeSettingsPaths: setBackdropSiteSettingsPaths, appTypeDetect: isBackdropApp, postImportDBAction: backdropPostImportDBAction, configOverrideAction: nil, postConfigAction: nil, postStartAction: backdropPostStartAction, importFilesAction: backdropImportFilesAction, defaultWorkingDirMap: docrootWorkingDir, 69 }, 70 71 nodeps.AppTypeCraftCms: { 72 uploadDir: nil, importFilesAction: craftCmsImportFilesAction, appTypeDetect: isCraftCmsApp, configOverrideAction: nil, postConfigAction: craftCmsPostConfigAction, postStartAction: craftCmsPostStartAction, 73 }, 74 75 nodeps.AppTypeDrupal6: { 76 settingsCreator: createDrupalSettingsPHP, uploadDir: getDrupalUploadDir, hookDefaultComments: getDrupal6Hooks, apptypeSettingsPaths: setDrupalSiteSettingsPaths, appTypeDetect: isDrupal6App, postImportDBAction: nil, configOverrideAction: drupal6ConfigOverrideAction, postConfigAction: nil, postStartAction: drupal6PostStartAction, importFilesAction: drupalImportFilesAction, defaultWorkingDirMap: docrootWorkingDir, 77 }, 78 79 nodeps.AppTypeDrupal7: { 80 settingsCreator: createDrupalSettingsPHP, uploadDir: getDrupalUploadDir, hookDefaultComments: getDrupal7Hooks, apptypeSettingsPaths: setDrupalSiteSettingsPaths, appTypeDetect: isDrupal7App, postImportDBAction: nil, configOverrideAction: nil, postConfigAction: nil, postStartAction: drupal7PostStartAction, importFilesAction: drupalImportFilesAction, defaultWorkingDirMap: docrootWorkingDir, 81 }, 82 83 nodeps.AppTypeDrupal8: { 84 settingsCreator: createDrupalSettingsPHP, uploadDir: getDrupalUploadDir, hookDefaultComments: getDrupal8Hooks, apptypeSettingsPaths: setDrupalSiteSettingsPaths, appTypeDetect: isDrupal8App, postImportDBAction: nil, configOverrideAction: drupal8ConfigOverrideAction, postConfigAction: nil, postStartAction: drupal8PostStartAction, importFilesAction: drupalImportFilesAction, 85 }, 86 87 nodeps.AppTypeDrupal9: { 88 settingsCreator: createDrupalSettingsPHP, uploadDir: getDrupalUploadDir, hookDefaultComments: getDrupal8Hooks, apptypeSettingsPaths: setDrupalSiteSettingsPaths, appTypeDetect: isDrupal9App, postImportDBAction: nil, configOverrideAction: nil, postConfigAction: nil, postStartAction: drupalPostStartAction, importFilesAction: drupalImportFilesAction, 89 }, 90 91 nodeps.AppTypeDrupal10: { 92 settingsCreator: createDrupalSettingsPHP, uploadDir: getDrupalUploadDir, hookDefaultComments: getDrupal8Hooks, apptypeSettingsPaths: setDrupalSiteSettingsPaths, appTypeDetect: isDrupal10App, postImportDBAction: nil, configOverrideAction: drupal10ConfigOverrideAction, postConfigAction: nil, postStartAction: drupalPostStartAction, importFilesAction: drupalImportFilesAction, 93 }, 94 95 nodeps.AppTypeLaravel: { 96 appTypeDetect: isLaravelApp, postStartAction: laravelPostStartAction, configOverrideAction: nil, 97 }, 98 99 nodeps.AppTypeMagento: { 100 settingsCreator: createMagentoSettingsFile, uploadDir: getMagentoUploadDir, hookDefaultComments: nil, apptypeSettingsPaths: setMagentoSiteSettingsPaths, appTypeDetect: isMagentoApp, postImportDBAction: nil, configOverrideAction: magentoConfigOverrideAction, postConfigAction: nil, postStartAction: nil, importFilesAction: magentoImportFilesAction, 101 }, 102 103 nodeps.AppTypeMagento2: { 104 settingsCreator: createMagento2SettingsFile, uploadDir: getMagento2UploadDir, hookDefaultComments: nil, apptypeSettingsPaths: setMagento2SiteSettingsPaths, appTypeDetect: isMagento2App, postImportDBAction: nil, configOverrideAction: magento2ConfigOverrideAction, postConfigAction: nil, postStartAction: nil, importFilesAction: magentoImportFilesAction, 105 }, 106 107 nodeps.AppTypePHP: { 108 uploadDir: getPHPUploadDir, postStartAction: phpPostStartAction, importFilesAction: phpImportFilesAction, 109 }, 110 111 nodeps.AppTypeShopware6: {settingsCreator: nil, appTypeDetect: isShopware6App, apptypeSettingsPaths: setShopware6SiteSettingsPaths, uploadDir: getShopwareUploadDir, configOverrideAction: nil, postStartAction: shopware6PostStartAction, importFilesAction: shopware6ImportFilesAction}, 112 113 nodeps.AppTypeTYPO3: { 114 settingsCreator: createTypo3SettingsFile, uploadDir: getTypo3UploadDir, hookDefaultComments: getTypo3Hooks, apptypeSettingsPaths: setTypo3SiteSettingsPaths, appTypeDetect: isTypo3App, postImportDBAction: nil, configOverrideAction: nil, postConfigAction: nil, postStartAction: nil, importFilesAction: typo3ImportFilesAction, 115 }, 116 117 nodeps.AppTypeWordPress: { 118 settingsCreator: createWordpressSettingsFile, uploadDir: getWordpressUploadDir, hookDefaultComments: getWordpressHooks, apptypeSettingsPaths: setWordpressSiteSettingsPaths, appTypeDetect: isWordpressApp, postImportDBAction: nil, configOverrideAction: nil, postConfigAction: nil, postStartAction: nil, importFilesAction: wordpressImportFilesAction, 119 }, 120 } 121 } 122 123 // CreateSettingsFile creates the settings file (like settings.php) for the 124 // provided app is the apptype has a settingsCreator function. 125 // It also preps the ddev directory, including setting up the .ddev gitignore 126 func (app *DdevApp) CreateSettingsFile() (string, error) { 127 err := PrepDdevDirectory(app) 128 if err != nil { 129 util.Warning("Unable to PrepDdevDirectory: %v", err) 130 } 131 132 app.SetApptypeSettingsPaths() 133 134 if app.DisableSettingsManagement && app.Type != nodeps.AppTypePHP { 135 util.Warning("Not creating CMS settings files because disable_settings_management=true") 136 return "", nil 137 } 138 139 // Drupal and WordPress love to change settings files to be unwriteable. 140 // Chmod them to something we can work with in the event that they already 141 // exist. 142 chmodTargets := []string{filepath.Dir(app.SiteSettingsPath), app.SiteDdevSettingsFile} 143 for _, fp := range chmodTargets { 144 fileInfo, err := os.Stat(fp) 145 if err != nil { 146 // We're not doing anything about this error other than warning, 147 // and will have to deal with the same check in settingsCreator. 148 if !os.IsNotExist(err) { 149 util.Warning("Unable to ensure write permissions: %v", err) 150 } 151 152 continue 153 } 154 155 perms := 0644 156 if fileInfo.IsDir() { 157 perms = 0755 158 } 159 160 err = os.Chmod(fp, os.FileMode(perms)) 161 if err != nil { 162 return "", fmt.Errorf("could not change permissions on file %s to make it writeable: %v", fp, err) 163 } 164 } 165 166 // If we have a function to do the settings creation, do it, otherwise 167 // just ignore. 168 if appFuncs, ok := appTypeMatrix[app.GetType()]; ok && appFuncs.settingsCreator != nil { 169 settingsPath, err := appFuncs.settingsCreator(app) 170 if err != nil { 171 util.Warning("Unable to create settings file '%s': %v", app.SiteSettingsPath, err) 172 } 173 174 // Don't create gitignore if it would be in top-level directory, where 175 // there is almost certainly already a gitignore (like backdrop) 176 if path.Dir(app.SiteSettingsPath) != app.AppRoot { 177 if err = CreateGitIgnore(filepath.Dir(app.SiteSettingsPath), filepath.Base(app.SiteDdevSettingsFile), "drushrc.php"); err != nil { 178 util.Warning("Failed to write .gitignore in %s: %v", filepath.Dir(app.SiteDdevSettingsFile), err) 179 } 180 } 181 return settingsPath, nil 182 } 183 err = app.MutagenSyncFlush() 184 if err != nil { 185 return "", err 186 } 187 188 return "", nil 189 } 190 191 // GetUploadDir returns the upload (public files) directory for the given app 192 func (app *DdevApp) GetUploadDir() string { 193 if appFuncs, ok := appTypeMatrix[app.GetType()]; ok && appFuncs.uploadDir != nil { 194 uploadDir := appFuncs.uploadDir(app) 195 return uploadDir 196 } 197 return app.UploadDir 198 } 199 200 // GetHookDefaultComments gets the actual text of the config.yaml hook suggestions 201 // for a given apptype 202 func (app *DdevApp) GetHookDefaultComments() []byte { 203 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.hookDefaultComments != nil { 204 suggestions := appFuncs.hookDefaultComments() 205 return suggestions 206 } 207 return []byte("") 208 } 209 210 // SetApptypeSettingsPaths chooses and sets the settings.php/settings.local.php 211 // and related paths for a given app. 212 func (app *DdevApp) SetApptypeSettingsPaths() { 213 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.apptypeSettingsPaths != nil { 214 appFuncs.apptypeSettingsPaths(app) 215 } 216 } 217 218 // DetectAppType calls each apptype's detector until it finds a match, 219 // or returns 'php' as a last resort. 220 func (app *DdevApp) DetectAppType() string { 221 for appName, appFuncs := range appTypeMatrix { 222 if appFuncs.appTypeDetect != nil && appFuncs.appTypeDetect(app) { 223 return appName 224 } 225 } 226 return nodeps.AppTypePHP 227 } 228 229 // PostImportDBAction calls each apptype's detector until it finds a match, 230 // or returns 'php' as a last resort. 231 func (app *DdevApp) PostImportDBAction() error { 232 233 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.postImportDBAction != nil { 234 return appFuncs.postImportDBAction(app) 235 } 236 237 return nil 238 } 239 240 // ConfigFileOverrideAction gives a chance for an apptype to override any element 241 // of config.yaml that it needs to (on initial creation, but not after that) 242 func (app *DdevApp) ConfigFileOverrideAction() error { 243 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.configOverrideAction != nil && !app.ConfigExists() { 244 return appFuncs.configOverrideAction(app) 245 } 246 247 return nil 248 } 249 250 // PostConfigAction gives a chance for an apptype to override do something at 251 // the end of ddev config. 252 func (app *DdevApp) PostConfigAction() error { 253 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.postConfigAction != nil { 254 return appFuncs.postConfigAction(app) 255 } 256 257 return nil 258 } 259 260 // PostStartAction gives a chance for an apptype to do something after the app 261 // has been started. 262 func (app *DdevApp) PostStartAction() error { 263 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.postStartAction != nil { 264 return appFuncs.postStartAction(app) 265 } 266 267 return nil 268 } 269 270 // ImportFilesAction executes the relevant import files workflow for each app type. 271 func (app *DdevApp) ImportFilesAction(importPath, extPath string) error { 272 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.importFilesAction != nil { 273 return appFuncs.importFilesAction(app, importPath, extPath) 274 } 275 276 return fmt.Errorf("this project type (%s) does not support import-files", app.Type) 277 } 278 279 // DefaultWorkingDirMap returns the app type's default working directory map. 280 func (app *DdevApp) DefaultWorkingDirMap() map[string]string { 281 _, _, username := util.GetContainerUIDGid() 282 // Default working directory values are defined here. 283 // Services working directories can be overridden by app types if needed. 284 defaults := map[string]string{ 285 "web": "/var/www/html/", 286 "db": "/home/" + username, 287 "dba": "/root", 288 } 289 290 if appFuncs, ok := appTypeMatrix[app.Type]; ok && appFuncs.defaultWorkingDirMap != nil { 291 return appFuncs.defaultWorkingDirMap(app, defaults) 292 } 293 294 if app.Database.Type == nodeps.Postgres { 295 defaults["db"] = "/var/lib/postgresql" 296 } 297 return defaults 298 } 299 300 // docrootWorkingDir handles the shared case in which the web service working directory is the docroot. 301 func docrootWorkingDir(app *DdevApp, defaults map[string]string) map[string]string { 302 defaults["web"] = path.Join("/var/www/html", app.Docroot) 303 304 return defaults 305 } 306 307 // IsValidAppType is a helper function to determine if an app type is valid, returning 308 // true if the given app type is valid and configured and false otherwise. 309 func IsValidAppType(apptype string) bool { 310 if _, ok := appTypeMatrix[apptype]; !ok { 311 return false 312 } 313 314 return true 315 } 316 317 // GetValidAppTypes returns the valid apptype keys from the appTypeMatrix 318 func GetValidAppTypes() []string { 319 keys := make([]string, 0, len(appTypeMatrix)) 320 for k := range appTypeMatrix { 321 keys = append(keys, k) 322 sort.Strings(keys) 323 } 324 return keys 325 }