From bdfd80dbf2dbaafe35b3e1f23512d64df1e3559f Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Tue, 9 Oct 2018 19:02:23 -0600 Subject: [PATCH] Validate the project name when it's entered, fixes #514, fixes #86 (#1147) * Validate also in non-interactive config * Add tests for valid and invalid project names --- cmd/ddev/cmd/config.go | 7 ++++++ cmd/ddev/cmd/config_test.go | 45 ++++++++++++++++++++++++++++++++++ pkg/ddevapp/config.go | 9 +++---- pkg/ddevapp/config_test.go | 18 +++++++++++++- pkg/ddevapp/providerDefault.go | 13 +++++++++- pkg/exec/exec.go | 1 + 6 files changed, 85 insertions(+), 8 deletions(-) diff --git a/cmd/ddev/cmd/config.go b/cmd/ddev/cmd/config.go index 53966107047..8739028dede 100644 --- a/cmd/ddev/cmd/config.go +++ b/cmd/ddev/cmd/config.go @@ -297,6 +297,13 @@ func handleMainConfigArgs(cmd *cobra.Command, args []string, app *ddevapp.DdevAp app.WebserverType = webserverTypeArg } + provider, err := app.GetProvider() + util.CheckErr(err) + err = provider.ValidateField("Name", app.Name) + if err != nil { + return fmt.Errorf("failed to validate configuration: %v", err) + } + err = app.WriteConfig() if err != nil { return fmt.Errorf("could not write ddev config file %s: %v", app.ConfigPath, err) diff --git a/cmd/ddev/cmd/config_test.go b/cmd/ddev/cmd/config_test.go index de52422c5e9..27f1856e0fd 100644 --- a/cmd/ddev/cmd/config_test.go +++ b/cmd/ddev/cmd/config_test.go @@ -158,3 +158,48 @@ func TestConfigSetValues(t *testing.T) { assert.Equal(uploadDir, app.UploadDir) assert.Equal(webserverType, app.WebserverType) } + +// TestConfigInvalidProjectname tests to make sure that invalid projectnames +// are not accepted and valid names are accepted. +func TestConfigInvalidProjectname(t *testing.T) { + var err error + assert := asrt.New(t) + + // Create a temporary directory and switch to it. + tmpdir := testcommon.CreateTmpDir(t.Name()) + defer testcommon.CleanupDir(tmpdir) + defer testcommon.Chdir(tmpdir)() + + // Create an existing docroot + docroot := "web" + if err = os.MkdirAll(filepath.Join(tmpdir, docroot), 0755); err != nil { + t.Errorf("Could not create docroot %s in %s", docroot, tmpdir) + } + + // Test some valid project names + for _, projName := range []string{"no-spaces-but-hyphens", "UpperAndLower", "should.work.with.dots"} { + args := []string{ + "config", + "--projectname", projName, + } + + out, err := exec.RunCommand(DdevBin, args) + assert.NoError(err) + assert.NotContains(out, "is not a valid project name") + assert.Contains(out, "You may now run 'ddev start'") + } + + // test some invalid project names. + for _, projName := range []string{"with spaces", "with_underscores", "no,commas-will-make-it"} { + args := []string{ + "config", + "--projectname", projName, + } + + out, err := exec.RunCommand(DdevBin, args) + assert.Error(err) + assert.Contains(out, fmt.Sprintf("%s is not a valid project name", projName)) + assert.NotContains(out, "You may now run 'ddev start'") + } + +} diff --git a/pkg/ddevapp/config.go b/pkg/ddevapp/config.go index 77fa7ee4bbd..30ab83a385c 100644 --- a/pkg/ddevapp/config.go +++ b/pkg/ddevapp/config.go @@ -280,7 +280,7 @@ func (app *DdevApp) ValidateConfig() error { // validate hostname match := hostRegex.MatchString(app.GetHostname()) if !match { - return fmt.Errorf("%s is not a valid hostname. Please enter a site name in your configuration that will allow for a valid hostname. See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames for valid hostname requirements", app.GetHostname()) + return fmt.Errorf("%s is not a valid hostname. Please enter a project name in your configuration that will allow for a valid hostname. See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames for valid hostname requirements", app.GetHostname()) } // validate apptype @@ -451,14 +451,13 @@ func (app *DdevApp) RenderComposeYAML() (string, error) { return doc.String(), err } -// Define an application name. +// prompt for a project name. func (app *DdevApp) promptForName() error { provider, err := app.GetProvider() if err != nil { return err } - namePrompt := "Project name" if app.Name == "" { dir, err := os.Getwd() // if working directory name is invalid for hostnames, we shouldn't suggest it @@ -468,9 +467,7 @@ func (app *DdevApp) promptForName() error { } } - namePrompt = fmt.Sprintf("%s (%s)", namePrompt, app.Name) - fmt.Print(namePrompt + ": ") - app.Name = util.GetInput(app.Name) + app.Name = util.Prompt("Project name", app.Name) return provider.ValidateField("Name", app.Name) } diff --git a/pkg/ddevapp/config_test.go b/pkg/ddevapp/config_test.go index a6adfa17c07..1e2c6d9c889 100644 --- a/pkg/ddevapp/config_test.go +++ b/pkg/ddevapp/config_test.go @@ -183,6 +183,22 @@ func TestConfigCommand(t *testing.T) { assert.Contains(out, testDir) assert.Contains(out, fmt.Sprintf("'%s' is not a valid project type", invalidAppType)) + // Create an example input buffer that writes an invalid projectname, then a valid-project-name, + // a valid document root, + // a valid app type + input = fmt.Sprintf("invalid_project_name\n%s\ndocroot\n%s", name, testValues[apptypePos]) + scanner = bufio.NewScanner(strings.NewReader(input)) + util.SetInputScanner(scanner) + + restoreOutput = testcommon.CaptureUserOut() + err = app.PromptForConfig() + assert.NoError(err, t) + out = restoreOutput() + + // Ensure we have expected vales in output. + assert.Contains(out, testDir) + assert.Contains(out, "invalid_project_name is not a valid project name") + // Ensure values were properly set on the app struct. assert.Equal(name, app.Name) assert.Equal(testValues[apptypePos], app.Type) @@ -447,7 +463,7 @@ func TestValidate(t *testing.T) { app.Name = "Invalid!" err = app.ValidateConfig() - assert.EqualError(err, fmt.Sprintf("%s is not a valid hostname. Please enter a site name in your configuration that will allow for a valid hostname. See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames for valid hostname requirements", app.GetHostname())) + assert.EqualError(err, fmt.Sprintf("%s is not a valid hostname. Please enter a project name in your configuration that will allow for a valid hostname. See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames for valid hostname requirements", app.GetHostname())) app.Docroot = "testdata" app.Name = "valid" diff --git a/pkg/ddevapp/providerDefault.go b/pkg/ddevapp/providerDefault.go index 1f7c080e726..23b72613910 100644 --- a/pkg/ddevapp/providerDefault.go +++ b/pkg/ddevapp/providerDefault.go @@ -1,6 +1,9 @@ package ddevapp -import "os" +import ( + "fmt" + "os" +) // DefaultProvider provides a no-op for the provider plugin interface methods. type DefaultProvider struct{} @@ -12,6 +15,14 @@ func (p *DefaultProvider) Init(app *DdevApp) error { // ValidateField provides a no-op for the ValidateField operation. func (p *DefaultProvider) ValidateField(field, value string) error { + if field == "Name" { + // validate project name + match := hostRegex.MatchString(value) + if !match { + return fmt.Errorf("%s is not a valid project name. Please enter a project name in your configuration that will allow for a valid hostname. See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames for valid hostname requirements", value) + } + + } return nil } diff --git a/pkg/exec/exec.go b/pkg/exec/exec.go index d10a0dddec4..de5bff6a846 100644 --- a/pkg/exec/exec.go +++ b/pkg/exec/exec.go @@ -9,6 +9,7 @@ import ( ) // RunCommand runs a command on the host system. +// returns the stdout of the command and an err func RunCommand(command string, args []string) (string, error) { output.UserOut.WithFields(log.Fields{ "Command": command + " " + strings.Join(args[:], " "),