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  }