github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/backdrop.go (about)

     1  package ddevapp
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"text/template"
     9  
    10  	"github.com/ddev/ddev/pkg/archive"
    11  	"github.com/ddev/ddev/pkg/dockerutil"
    12  	"github.com/ddev/ddev/pkg/fileutil"
    13  	"github.com/ddev/ddev/pkg/nodeps"
    14  	"github.com/ddev/ddev/pkg/output"
    15  	"github.com/ddev/ddev/pkg/util"
    16  )
    17  
    18  // BackdropSettings holds database connection details for Backdrop.
    19  type BackdropSettings struct {
    20  	DatabaseName     string
    21  	DatabaseUsername string
    22  	DatabasePassword string
    23  	DatabaseHost     string
    24  	DatabaseDriver   string
    25  	DatabasePort     string
    26  	HashSalt         string
    27  	Signature        string
    28  	SiteSettings     string
    29  	SiteSettingsDdev string
    30  	DockerIP         string
    31  	DBPublishedPort  int
    32  }
    33  
    34  // NewBackdropSettings produces a BackdropSettings object with default values.
    35  func NewBackdropSettings(app *DdevApp) *BackdropSettings {
    36  	dockerIP, _ := dockerutil.GetDockerIP()
    37  	dbPublishedPort, _ := app.GetPublishedPort("db")
    38  
    39  	return &BackdropSettings{
    40  		DatabaseName:     "db",
    41  		DatabaseUsername: "db",
    42  		DatabasePassword: "db",
    43  		DatabaseHost:     "ddev-" + app.Name + "-db",
    44  		DatabaseDriver:   "mysql",
    45  		DatabasePort:     GetExposedPort(app, "db"),
    46  		HashSalt:         util.HashSalt(app.Name),
    47  		Signature:        nodeps.DdevFileSignature,
    48  		SiteSettings:     "settings.php",
    49  		SiteSettingsDdev: "settings.ddev.php",
    50  		DockerIP:         dockerIP,
    51  		DBPublishedPort:  dbPublishedPort,
    52  	}
    53  }
    54  
    55  // createBackdropSettingsFile manages creation and modification of settings.php and settings.ddev.php.
    56  // If a settings.php file already exists, it will be modified to ensure that it includes
    57  // settings.ddev.php, which contains ddev-specific configuration.
    58  func createBackdropSettingsFile(app *DdevApp) (string, error) {
    59  	settings := NewBackdropSettings(app)
    60  
    61  	if !fileutil.FileExists(app.SiteSettingsPath) {
    62  		output.UserOut.Printf("No %s file exists, creating one", settings.SiteSettings)
    63  		if err := writeDrupalSettingsPHP(app.SiteSettingsPath, app.Type); err != nil {
    64  			return "", err
    65  		}
    66  	}
    67  
    68  	included, err := fileutil.FgrepStringInFile(app.SiteSettingsPath, settings.SiteSettingsDdev)
    69  	if err != nil {
    70  		return "", err
    71  	}
    72  
    73  	if included {
    74  		output.UserOut.Printf("Existing %s includes %s", settings.SiteSettings, settings.SiteSettingsDdev)
    75  	} else {
    76  		output.UserOut.Printf("Existing %s file does not include %s, modifying to include ddev settings", settings.SiteSettings, settings.SiteSettingsDdev)
    77  
    78  		if err = appendIncludeToDrupalSettingsFile(app.SiteSettingsPath, app.Type); err != nil {
    79  			return "", fmt.Errorf("failed to include %s in %s: %v", settings.SiteSettingsDdev, settings.SiteSettings, err)
    80  		}
    81  	}
    82  
    83  	if err = writeBackdropSettingsDdevPHP(settings, app.SiteDdevSettingsFile, app); err != nil {
    84  		return "", fmt.Errorf("failed to write Drupal settings file %s: %v", app.SiteDdevSettingsFile, err)
    85  	}
    86  
    87  	return app.SiteDdevSettingsFile, nil
    88  }
    89  
    90  // writeBackdropSettingsDdevPHP dynamically produces a valid settings.ddev.php file
    91  // by combining a configuration object with a data-driven template.
    92  func writeBackdropSettingsDdevPHP(settings *BackdropSettings, filePath string, _ *DdevApp) error {
    93  	if fileutil.FileExists(filePath) {
    94  		// Check if the file is managed by ddev.
    95  		signatureFound, err := fileutil.FgrepStringInFile(filePath, nodeps.DdevFileSignature)
    96  		if err != nil {
    97  			return err
    98  		}
    99  
   100  		// If the signature wasn't found, warn the user and return.
   101  		if !signatureFound {
   102  			util.Warning("%s already exists and is managed by the user.", filepath.Base(filePath))
   103  			return nil
   104  		}
   105  	}
   106  	t, err := template.New("settings.ddev.php").ParseFS(bundledAssets, path.Join("drupal/backdrop/settings.ddev.php"))
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	// Ensure target directory exists and is writable
   112  	dir := filepath.Dir(filePath)
   113  	if err = os.Chmod(dir, 0755); os.IsNotExist(err) {
   114  		if err = os.MkdirAll(dir, 0755); err != nil {
   115  			return err
   116  		}
   117  	} else if err != nil {
   118  		return err
   119  	}
   120  
   121  	file, err := os.Create(filePath)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	defer util.CheckClose(file)
   126  
   127  	err = t.Execute(file, settings)
   128  	return err
   129  }
   130  
   131  // getBackdropUploadDirs will return the default paths.
   132  func getBackdropUploadDirs(_ *DdevApp) []string {
   133  	return []string{"files"}
   134  }
   135  
   136  // getBackdropHooks for appending as byte array.
   137  func getBackdropHooks() []byte {
   138  	backdropHooks := `#  post-import-db:
   139  #    - exec: drush cc all
   140  `
   141  	return []byte(backdropHooks)
   142  }
   143  
   144  // setBackdropSiteSettingsPaths sets the paths to settings.php for templating.
   145  func setBackdropSiteSettingsPaths(app *DdevApp) {
   146  	settings := NewBackdropSettings(app)
   147  	settingsFileBasePath := filepath.Join(app.AppRoot, app.Docroot)
   148  	app.SiteSettingsPath = filepath.Join(settingsFileBasePath, settings.SiteSettings)
   149  	app.SiteDdevSettingsFile = filepath.Join(settingsFileBasePath, settings.SiteSettingsDdev)
   150  }
   151  
   152  // isBackdropApp returns true if the app is of type "backdrop".
   153  func isBackdropApp(app *DdevApp) bool {
   154  	if _, err := os.Stat(filepath.Join(app.AppRoot, app.Docroot, "core/scripts/backdrop.sh")); err == nil {
   155  		return true
   156  	}
   157  	return false
   158  }
   159  
   160  // backdropPostImportDBAction emits a warning about moving configuration into place
   161  // appropriately in order for Backdrop to function properly.
   162  func backdropPostImportDBAction(_ *DdevApp) error {
   163  	util.Warning("Backdrop sites require your config JSON files to be located in your site's \"active\" configuration directory. Please refer to the Backdrop documentation (https://backdropcms.org/user-guide/moving-backdrop-site) for more information about this process.")
   164  	return nil
   165  }
   166  
   167  // backdropImportFilesAction defines the Backdrop workflow for importing project files.
   168  // The Backdrop workflow is currently identical to the Drupal import-files workflow.
   169  func backdropImportFilesAction(app *DdevApp, uploadDir, importPath, extPath string) error {
   170  	destPath := app.calculateHostUploadDirFullPath(uploadDir)
   171  
   172  	// parent of destination dir should exist
   173  	if !fileutil.FileExists(filepath.Dir(destPath)) {
   174  		return fmt.Errorf("unable to import to %s: parent directory does not exist", destPath)
   175  	}
   176  
   177  	// parent of destination dir should be writable.
   178  	if err := os.Chmod(filepath.Dir(destPath), 0755); err != nil {
   179  		return err
   180  	}
   181  
   182  	// If the destination path exists, remove it as was warned
   183  	if fileutil.FileExists(destPath) {
   184  		if err := os.RemoveAll(destPath); err != nil {
   185  			return fmt.Errorf("failed to cleanup %s before import: %v", destPath, err)
   186  		}
   187  	}
   188  
   189  	if isTar(importPath) {
   190  		if err := archive.Untar(importPath, destPath, extPath); err != nil {
   191  			return fmt.Errorf("failed to extract provided archive: %v", err)
   192  		}
   193  
   194  		return nil
   195  	}
   196  
   197  	if isZip(importPath) {
   198  		if err := archive.Unzip(importPath, destPath, extPath); err != nil {
   199  			return fmt.Errorf("failed to extract provided archive: %v", err)
   200  		}
   201  
   202  		return nil
   203  	}
   204  
   205  	//nolint: revive
   206  	if err := fileutil.CopyDir(importPath, destPath); err != nil {
   207  		return err
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  // backdropPostStartAction handles default post-start actions for Backdrop apps, like ensuring
   214  // useful permissions settings on sites/default.
   215  func backdropPostStartAction(app *DdevApp) error {
   216  	// Drush config has to be written after start because we don't know the ports until it's started
   217  	err := WriteDrushrc(app, filepath.Join(filepath.Dir(app.SiteSettingsPath), "drushrc.php"))
   218  	if err != nil {
   219  		util.Warning("Failed to WriteDrushrc: %v", err)
   220  	}
   221  
   222  	if _, err = app.CreateSettingsFile(); err != nil {
   223  		return fmt.Errorf("failed to write settings file %s: %v", app.SiteDdevSettingsFile, err)
   224  	}
   225  	return nil
   226  }
   227  
   228  // getBackdropComposerCreateAllowedPaths returns fullpaths that are allowed to be present when running composer create
   229  func getBackdropComposerCreateAllowedPaths(app *DdevApp) ([]string, error) {
   230  	var allowed []string
   231  
   232  	// drushrc.php path
   233  	allowed = append(allowed, filepath.Join(filepath.Dir(app.SiteSettingsPath), "drushrc.php"))
   234  
   235  	return allowed, nil
   236  }