Skip to content

Commit

Permalink
feat: Add ddev config --update flag, fixes #6035 (#6055)
Browse files Browse the repository at this point in the history
Co-authored-by: Randy Fay <randy@randyfay.com>
  • Loading branch information
penyaskito and rfay committed Apr 11, 2024
1 parent 4bb8255 commit d04b047
Show file tree
Hide file tree
Showing 24 changed files with 2,876 additions and 23 deletions.
2 changes: 2 additions & 0 deletions cmd/ddev/cmd/composer-create.go
Expand Up @@ -300,6 +300,8 @@ ddev composer create --preserve-flags --no-interaction psr/log
util.Warning("Failed to restart project after composer create: %v", err)
}

util.Success("\nddev composer create was successful.\nConsider using `ddev config --update` to autodetect configuration for your project")

if runtime.GOOS == "windows" {
fileutil.ReplaceSimulatedLinks(app.AppRoot)
}
Expand Down
16 changes: 14 additions & 2 deletions cmd/ddev/cmd/config.go
Expand Up @@ -309,6 +309,7 @@ func init() {
ConfigCommand.Flags().Bool("disable-upload-dirs-warning", true, `Disable warnings about upload-dirs not being set when using performance-mode=mutagen.`)
ConfigCommand.Flags().StringVar(&ddevVersionConstraint, "ddev-version-constraint", "", `Specify a ddev version constraint to validate ddev against.`)
ConfigCommand.Flags().Bool("corepack-enable", true, `Do 'corepack enable' to enable latest yarn/pnpm'`)
ConfigCommand.Flags().Bool("update", false, `Update project settings based on detection and project-type overrides`)

RootCmd.AddCommand(ConfigCommand)

Expand Down Expand Up @@ -423,10 +424,21 @@ func handleMainConfigArgs(cmd *cobra.Command, _ []string, app *ddevapp.DdevApp)
util.Failed("Failed to get absolute path to Docroot %s: %v", app.Docroot, pathErr)
}

doUpdate, _ := cmd.Flags().GetBool("update")
switch {
case doUpdate:
if projectTypeArg == "" {
projectTypeArg = detectedApptype
}

app.Type = projectTypeArg
util.Success("Auto-updating project configuration because update is requested.\nConfiguring a '%s' project with docroot '%s' at %s", app.Type, app.Docroot, fullPath)
err = app.ConfigFileOverrideAction(true)
if err != nil {
util.Warning("ConfigOverrideAction failed: %v")
}
case app.Type != nodeps.AppTypeNone && projectTypeArg == "" && detectedApptype != app.Type: // apptype was not passed, but we found an app of a different type
util.Warning("A project of type '%s' was found in %s, but the project is configured with type '%s'", detectedApptype, fullPath, app.Type)
break
default:
if projectTypeArg == "" {
projectTypeArg = detectedApptype
Expand All @@ -438,7 +450,7 @@ func handleMainConfigArgs(cmd *cobra.Command, _ []string, app *ddevapp.DdevApp)

// App overrides are done after app type is detected, but
// before user-defined flags are set.
err = app.ConfigFileOverrideAction()
err = app.ConfigFileOverrideAction(false)
if err != nil {
util.Failed("Failed to run ConfigFileOverrideAction: %v", err)
}
Expand Down
111 changes: 105 additions & 6 deletions cmd/ddev/cmd/config_test.go
Expand Up @@ -2,8 +2,10 @@ package cmd

import (
"fmt"
copy2 "github.com/otiai10/copy"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -155,8 +157,8 @@ func TestConfigSetValues(t *testing.T) {
projectName := t.Name()
projectType := nodeps.AppTypeTYPO3
phpVersion := nodeps.PHP71
httpPort := "81"
httpsPort := "444"
routerHTTPPort := "81"
routerHTTPSPort := "444"
hostDBPort := "60001"
hostWebserverPort := "60002"
hostHTTPSPort := "60003"
Expand Down Expand Up @@ -196,8 +198,8 @@ func TestConfigSetValues(t *testing.T) {
"--php-version", phpVersion,
"--composer-root", composerRoot,
"--composer-version", composerVersion,
"--http-port", httpPort,
"--https-port", httpsPort,
"--router-http-port", routerHTTPPort,
"--router-https-port", routerHTTPSPort,
fmt.Sprintf("--xdebug-enabled=%t", xdebugEnabled),
fmt.Sprintf("--no-project-mount=%t", noProjectMount),
"--additional-hostnames", additionalHostnames,
Expand All @@ -223,6 +225,7 @@ func TestConfigSetValues(t *testing.T) {
"--disable-upload-dirs-warning",
}

t.Logf("command=\n%s", strings.Join(args, " "))
out, err := exec.RunHostCommand(DdevBin, args...)
assert.NoError(err, "error running ddev %v: %v, output=%s", args, err, out)

Expand All @@ -248,8 +251,8 @@ func TestConfigSetValues(t *testing.T) {
assert.Equal(phpVersion, app.PHPVersion)
assert.Equal(composerRoot, app.ComposerRoot)
assert.Equal(composerVersion, app.ComposerVersion)
assert.Equal(httpPort, app.RouterHTTPPort)
assert.Equal(httpsPort, app.RouterHTTPSPort)
assert.Equal(routerHTTPPort, app.RouterHTTPPort)
assert.Equal(routerHTTPSPort, app.RouterHTTPSPort)
assert.Equal(hostWebserverPort, app.HostWebserverPort)
assert.Equal(hostDBPort, app.HostDBPort)
assert.Equal(xdebugEnabled, app.XdebugEnabled)
Expand Down Expand Up @@ -546,6 +549,102 @@ func TestConfigDatabaseVersion(t *testing.T) {
}
}

// TestConfigUpdate verifies that ddev config --update does the right things updating default
// config, and does not do the wrong things.
func TestConfigUpdate(t *testing.T) {
var err error
origDir, _ := os.Getwd()

// Create a temporary directory and switch to it.
testDir := testcommon.CreateTmpDir(t.Name())

t.Cleanup(func() {
app, _ := ddevapp.NewApp(testDir, false)
_ = app.Stop(true, false)
_ = os.Chdir(origDir)
_ = os.RemoveAll(testDir)
})
tests := map[string]struct {
input string
baseExpectation ddevapp.DdevApp
configExpectation ddevapp.DdevApp
}{
"drupal11-composer": {
baseExpectation: ddevapp.DdevApp{Type: nodeps.AppTypePHP, PHPVersion: nodeps.PHPDefault, Docroot: "", CorepackEnable: false, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
configExpectation: ddevapp.DdevApp{Type: nodeps.AppTypeDrupal, PHPVersion: nodeps.PHP83, Docroot: "web", CorepackEnable: true, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
},
"drupal11-git": {
baseExpectation: ddevapp.DdevApp{Type: nodeps.AppTypePHP, PHPVersion: nodeps.PHPDefault, Docroot: "", CorepackEnable: false, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
configExpectation: ddevapp.DdevApp{Type: nodeps.AppTypeDrupal, PHPVersion: nodeps.PHP83, Docroot: "", CorepackEnable: true, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
},
"drupal10-composer": {
baseExpectation: ddevapp.DdevApp{Type: nodeps.AppTypePHP, PHPVersion: nodeps.PHPDefault, Docroot: "", CorepackEnable: false, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
configExpectation: ddevapp.DdevApp{Type: nodeps.AppTypeDrupal, PHPVersion: nodeps.PHP83, Docroot: "web", CorepackEnable: false, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
},
"craftcms": {
baseExpectation: ddevapp.DdevApp{Type: nodeps.AppTypePHP, PHPVersion: nodeps.PHPDefault, Docroot: "", CorepackEnable: false, Database: ddevapp.DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDBDefaultVersion}},
configExpectation: ddevapp.DdevApp{Type: nodeps.AppTypeCraftCms, PHPVersion: nodeps.PHPDefault, Docroot: "web", CorepackEnable: false, Database: ddevapp.DatabaseDesc{Type: nodeps.MySQL, Version: "8.0"}},
},
}

for testName, expectation := range tests {
t.Run(testName, func(t *testing.T) {
// Delete existing
_ = globalconfig.RemoveProjectInfo(t.Name())
// Delete filesystem from existing
_ = os.RemoveAll(testDir)

err = os.MkdirAll(testDir, 0755)
require.NoError(t, err)
_ = os.Chdir(testDir)
require.NoError(t, err)

// Copy testdata in from source
testSource := filepath.Join(origDir, "testdata", t.Name())
err = copy2.Copy(testSource, testDir)
require.NoError(t, err)

// Start with an existing config.yaml and verify
app, err := ddevapp.NewApp("", false)
require.NoError(t, err)
_ = app.Stop(true, false)

// Original values should match
checkValues(t, testName, expectation.baseExpectation, app)

// ddev config --update and verify
out, err := exec.RunHostCommand(DdevBin, "config", "--update")
require.NoError(t, err, "failed to run ddev config --update: %v output=%s", err, out)

// Load the newly-created app to inspect it
app, err = ddevapp.NewApp("", false)
require.NoError(t, err)

// Updated values should match
checkValues(t, testName, expectation.configExpectation, app)

})
}
}

// checkValues compares several values of the expected and actual apps to make sure they're the same
func checkValues(t *testing.T, name string, expectation ddevapp.DdevApp, app *ddevapp.DdevApp) {
assert := asrt.New(t)

reflectedExpectation := reflect.ValueOf(expectation)
reflectedApp := reflect.ValueOf(*app)

for _, member := range []string{"Type", "PHPVersion", "Docroot", "CorepackEnable", "Database"} {

fieldExpectation := reflectedExpectation.FieldByName(member)
if fieldExpectation.IsValid() {
fieldValueExpectation := fieldExpectation.Interface()
fieldValueApp := reflectedApp.FieldByName(member).Interface()
assert.Equal(fieldValueExpectation, fieldValueApp, "%s: field %s does not match", name, member)
}
}
}

// TestConfigGitignore checks that our gitignore is ignoring the right things.
func TestConfigGitignore(t *testing.T) {
assert := asrt.New(t)
Expand Down
@@ -0,0 +1 @@
type: php
14 changes: 14 additions & 0 deletions cmd/ddev/cmd/testdata/TestConfigUpdate/craftcms/craft
@@ -0,0 +1,14 @@
#!/usr/bin/env php
<?php
/**
* Craft console bootstrap file
*/

// Load shared bootstrap
require __DIR__ . '/bootstrap.php';

// Load and run Craft
/** @var craft\console\Application $app */
$app = require CRAFT_VENDOR_PATH . '/craftcms/cms/bootstrap/console.php';
$exitCode = $app->run();
exit($exitCode);

0 comments on commit d04b047

Please sign in to comment.