Skip to content

Commit

Permalink
Allow command-line arguments for "ddev config", fixes #476 (#495)
Browse files Browse the repository at this point in the history
  • Loading branch information
rfay committed Oct 17, 2017
1 parent 8bf72da commit 4ca6d38
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 75 deletions.
106 changes: 93 additions & 13 deletions cmd/ddev/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,30 @@ import (
"log"
"os"

"path/filepath"

"path"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/util"
"github.com/spf13/cobra"
)

// docrootRelPath is the relative path to the docroot where index.php is
var docrootRelPath string

// siteName is the name of the site
var siteName string

// pantheonEnvironment is the environment for pantheon, dev/test/prod
var pantheonEnvironment string

// fallbackPantheonEnvironment is our assumption that "dev" will be available in any case
const fallbackPantheonEnvironment = "dev"

// appType is the ddev app type, like drupal7/drupal8/wordpress
var appType string

// ConfigCommand represents the `ddev config` command
var ConfigCommand = &cobra.Command{
Use: "config [provider]",
Expand All @@ -33,26 +52,82 @@ var ConfigCommand = &cobra.Command{

c, err := ddevapp.NewConfig(appRoot, provider)
if err != nil {
// If there is an error reading the config and the file exists, we're not sure
// how to proceed.
if c.ConfigExists() {
util.Failed("Could not read config: %v", err)
}
util.Failed("Could not create new config: %v", err)
}

// Set the provider value after load so we can ensure we use the passed in provider value
// for this configuration.
c.Provider = provider
// If they have not given us any flags, we prompt for full info. Otherwise, we assume they're in control.
if siteName == "" && docrootRelPath == "" && pantheonEnvironment == "" && appType == "" {
err = c.PromptForConfig()
if err != nil {
util.Failed("There was a problem configuring your application: %v\n", err)
}
} else { // In this case we have to validate the provided items, or set to sane defaults

err = c.Config()
if err != nil {
util.Failed("There was a problem configuring your application: %v\n", err)
}
// Let them know if we're replacing the config.yaml
c.WarnIfConfigReplace()

// c.Name gets set to basename if not provided, or set to sitneName if provided
if c.Name != "" && siteName == "" { // If we already have a c.Name and no siteName, leave c.Name alone
// Sorry this is empty but it makes the logic clearer.
} else if siteName != "" { // if we have a siteName passed in, use it for c.Name
c.Name = siteName
} else { // No siteName passed, c.Name not set: use c.Name from the directory
// nolint: vetshadow
pwd, err := os.Getwd()
util.CheckErr(err)
c.Name = path.Base(pwd)
}

// docrootRelPath must exist
if docrootRelPath != "" {
c.Docroot = docrootRelPath
if _, err = os.Stat(docrootRelPath); os.IsNotExist(err) {
util.Failed("The docroot provided (%v) does not exist", docrootRelPath)
}
}
// pantheonEnvironment must be appropriate, and can only be used with pantheon provider.
if provider != "pantheon" && pantheonEnvironment != "" {
util.Failed("--pantheon-environment can only be used with pantheon provider, for example ddev config pantheon --pantheon-environment=dev --docroot=docroot")
}
if appType != "" && appType != "drupal7" && appType != "drupal8" && appType != "wordpress" {
util.Failed("apptype must be drupal7, drupal8, or wordpress")
}

foundAppType, err := ddevapp.DetermineAppType(c.Docroot)
fullPath, _ := filepath.Abs(c.Docroot)
if err == nil && (appType == "" || appType == foundAppType) { // Found an app, matches passed-in or no apptype passed
appType = foundAppType
util.Success("Found a %s codebase at %s\n", foundAppType, fullPath)
} else if appType != "" && err != nil { // apptype was passed, but we found no app at all
util.Warning("You have specified an apptype of %s but no app of that type is found in %s", appType, fullPath)
} else if appType != "" && err == nil && foundAppType != appType { // apptype was passed, app was found, but not the same type
util.Warning("You have specified an apptype of %s but an app of type %s was discovered in %s", appType, foundAppType, fullPath)
} else {
util.Failed("Failed to determine app type (drupal7/drupal8/wordpress).\nYour docroot %v may be incorrect - looking in directory %v, error=%v", c.Docroot, fullPath, err)
}
c.AppType = appType

prov, _ := c.GetProvider()

if provider == "pantheon" {
pantheonProvider := prov.(*ddevapp.PantheonProvider)
if pantheonEnvironment == "" {
pantheonEnvironment = fallbackPantheonEnvironment // assume a basic default if they haven't provided one.
}
pantheonProvider.SetSiteNameAndEnv(pantheonEnvironment)
}
// But pantheon *does* validate "Name"
err = prov.Validate()
if err != nil {
util.Failed("Failed to validate sitename %v and environment %v with provider %v: %v", c.Name, pantheonEnvironment, provider, err)
} else {
util.Success("Using pantheon sitename '%s' and environment '%s'.", c.Name, pantheonEnvironment)
}

}
err = c.Write()
if err != nil {
util.Failed("Could not write ddev config file: %v\n", err)

}

// If a provider is specified, prompt about whether to do an import after config.
Expand All @@ -66,5 +141,10 @@ var ConfigCommand = &cobra.Command{
}

func init() {
ConfigCommand.Flags().StringVarP(&siteName, "sitename", "", "", "Provide the sitename of site to configure (normally the same as the directory name)")
ConfigCommand.Flags().StringVarP(&docrootRelPath, "docroot", "", "", "Provide the relative docroot of the site, like 'docroot' or 'htdocs' or 'web', defaults to empty, the current directory")
ConfigCommand.Flags().StringVarP(&pantheonEnvironment, "pantheon-environment", "", "", "Choose the environment for a Pantheon site (dev/test/prod) (Pantheon-only)")
ConfigCommand.Flags().StringVarP(&appType, "apptype", "", "", "Provide the app type (like wordpress or drupal7 or drupal8). This is normally autodetected and this flag is not necessary")

RootCmd.AddCommand(ConfigCommand)
}
55 changes: 34 additions & 21 deletions pkg/ddevapp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ type Command struct {
type Provider interface {
Init(*Config) error
ValidateField(string, string) error
Config() error
PromptForConfig() error
Write(string) error
Read(string) error
Validate() error
GetBackup(string) (fileLocation string, importPath string, err error)
}

// NewConfig creates a new Config struct with defaults set. It is preferred to using new() directly.
// NewConfig creates a new Config struct with defaults set and overridden by any existing config.yml.
func NewConfig(AppRoot string, provider string) (*Config, error) {
// Set defaults.
c := &Config{}
Expand All @@ -95,19 +95,27 @@ func NewConfig(AppRoot string, provider string) (*Config, error) {
c.DBImage = version.DBImg + ":" + version.DBTag
c.DBAImage = version.DBAImg + ":" + version.DBATag

c.Provider = provider

if c.Provider == "" {
c.Provider = DefaultProviderName
}
// Load from file if available. This will return an error if the file doesn't exist,
// and it is up to the caller to determine if that's an issue.
err := c.Read()
if err != nil {
return c, fmt.Errorf("Unable to read config.yaml, %v, err=%v", c.ConfigPath, err)
if _, err := os.Stat(c.ConfigPath); !os.IsNotExist(err) {
err = c.Read()
if err != nil {
return c, fmt.Errorf("%v exists but cannot be read: %v", c.ConfigPath, err)
}
}

// Allow override with "pantheon" from function provider arg, but nothing else.
// Otherwise we accept whatever might have been in config file if there was anything.
switch {
case provider == "" || provider == DefaultProviderName:
c.Provider = DefaultProviderName
case provider == "pantheon":
c.Provider = "pantheon"
default:
return c, fmt.Errorf("Provider '%s' is not implemented", provider)
}

return c, err
return c, nil
}

// GetProvider returns a pointer to the provider instance interface.
Expand Down Expand Up @@ -219,15 +227,20 @@ func (c *Config) Read() error {
return err
}

// Config goes through a set of prompts to receive user input and generate an Config struct.
func (c *Config) Config() error {

// WarnIfConfigReplace just messages user about whether config is being replaced or created
func (c *Config) WarnIfConfigReplace() {
if c.ConfigExists() {
util.Warning("You are re-configuring %s. The existing configuration will be replaced.\n\n", c.AppRoot)
util.Warning("You are re-configuring the app at %s. \nThe existing configuration will be updated and replaced.\n\n", c.AppRoot)
} else {
fmt.Printf("Creating a new ddev project config in the current directory (%s)\n", c.AppRoot)
fmt.Printf("Once completed, your configuration will be written to %s\n\n\n", c.ConfigPath)
util.Success("Creating a new ddev project config in the current directory (%s)", c.AppRoot)
util.Success("Once completed, your configuration will be written to %s\n", c.ConfigPath)
}
}

// PromptForConfig goes through a set of prompts to receive user input and generate an Config struct.
func (c *Config) PromptForConfig() error {

c.WarnIfConfigReplace()

for {
err := c.namePrompt()
Expand All @@ -254,7 +267,7 @@ func (c *Config) Config() error {
return err
}

err = c.providerInstance.Config()
err = c.providerInstance.PromptForConfig()

return err
}
Expand Down Expand Up @@ -413,7 +426,7 @@ func (c *Config) appTypePrompt() error {
"Location": absDocroot,
}).Debug("Attempting to auto-determine application type")

appType, err = determineAppType(absDocroot)
appType, err = DetermineAppType(absDocroot)
if err == nil {
// If we found an application type just set it and inform the user.
util.Success("Found a %s codebase at %s\n", appType, filepath.Join(c.AppRoot, c.Docroot))
Expand Down Expand Up @@ -463,7 +476,7 @@ func PrepDdevDirectory(dir string) error {

// DetermineAppType uses some predetermined file checks to determine if a local app
// is of any of the known types
func determineAppType(basePath string) (string, error) {
func DetermineAppType(basePath string) (string, error) {
defaultLocations := map[string]string{
"scripts/drupal.sh": "drupal7",
"core/scripts/drupal.sh": "drupal8",
Expand All @@ -485,7 +498,7 @@ func determineAppType(basePath string) (string, error) {
}
}

return "", errors.New("determineAppType() couldn't determine app's type")
return "", errors.New("DetermineAppType() couldn't determine app's type")
}

// setSiteSettingsPath determines the location for site's db settings file based on apptype.
Expand Down
19 changes: 7 additions & 12 deletions pkg/ddevapp/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ func TestNewConfig(t *testing.T) {

// Load a new Config
newConfig, err := NewConfig(testDir, DefaultProviderName)

// An error should be returned because no config file is present.
assert.Error(err)
assert.NoError(err)

// Ensure the config uses specified defaults.
assert.Equal(newConfig.APIVersion, CurrentAppVersion)
Expand All @@ -53,7 +51,6 @@ func TestNewConfig(t *testing.T) {
assert.NoError(err)

loadedConfig, err := NewConfig(testDir, DefaultProviderName)
// There should be no error this time, since the config should be available for loading.
assert.NoError(err)
assert.Equal(newConfig.Name, loadedConfig.Name)
assert.Equal(newConfig.AppType, loadedConfig.AppType)
Expand Down Expand Up @@ -82,8 +79,7 @@ func TestPrepDirectory(t *testing.T) {
defer testcommon.Chdir(testDir)()

config, err := NewConfig(testDir, DefaultProviderName)
// We should get an error here, since no config exists.
assert.Error(err)
assert.NoError(err)

// Prep the directory.
err = PrepDdevDirectory(filepath.Dir(config.ConfigPath))
Expand All @@ -101,7 +97,7 @@ func TestHostName(t *testing.T) {
defer testcommon.CleanupDir(testDir)
defer testcommon.Chdir(testDir)()
config, err := NewConfig(testDir, DefaultProviderName)
assert.Error(err)
assert.NoError(err)
config.Name = util.RandString(32)

assert.Equal(config.Hostname(), config.Name+"."+DDevTLD)
Expand All @@ -117,7 +113,7 @@ func TestWriteDockerComposeYaml(t *testing.T) {

// Create a config
config, err := NewConfig(testDir, DefaultProviderName)
assert.Error(err)
assert.NoError(err)
config.Name = util.RandString(32)
config.AppType = AllowedAppTypes[0]

Expand Down Expand Up @@ -159,9 +155,9 @@ func TestConfigCommand(t *testing.T) {
}

// Create the ddevapp we'll use for testing.
// This should return an error, since no existing config can be read.
// This will not return an error, since there is no existing config.
config, err := NewConfig(testDir, DefaultProviderName)
assert.Error(err)
assert.NoError(err)

// Randomize some values to use for Stdin during testing.
name := strings.ToLower(util.RandString(16))
Expand All @@ -175,12 +171,11 @@ func TestConfigCommand(t *testing.T) {
util.SetInputScanner(scanner)

restoreOutput := testcommon.CaptureStdOut()
err = config.Config()
err = config.PromptForConfig()
assert.NoError(err, t)
out := restoreOutput()

// Ensure we have expected vales in output.
assert.Contains(out, "Creating a new ddev project")
assert.Contains(out, testDir)
assert.Contains(out, fmt.Sprintf("No directory could be found at %s", filepath.Join(testDir, invalidDir)))
assert.Contains(out, fmt.Sprintf("%s is not a valid application type", invalidAppType))
Expand Down
11 changes: 4 additions & 7 deletions pkg/ddevapp/pantheon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ func TestPantheonConfigCommand(t *testing.T) {
}

// Create the ddevapp we'll use for testing.
// This should return an error, since no existing config can be read.
config, err := NewConfig(testDir, "pantheon")
assert.Error(err)
assert.NoError(err)

// Randomize some values to use for Stdin during testing.
invalidName := strings.ToLower(util.RandString(16))
Expand All @@ -70,7 +69,7 @@ func TestPantheonConfigCommand(t *testing.T) {
util.SetInputScanner(scanner)

restoreOutput := testcommon.CaptureStdOut()
err = config.Config()
err = config.PromptForConfig()
assert.NoError(err, t)
out := restoreOutput()

Expand All @@ -81,7 +80,6 @@ func TestPantheonConfigCommand(t *testing.T) {
assert.NoError(err)

// Ensure we have expected string values in output.
assert.Contains(out, "Creating a new ddev project")
assert.Contains(out, testDir)
assert.Contains(out, fmt.Sprintf("could not find a pantheon site named %s", invalidName))
assert.Contains(out, fmt.Sprintf("could not find an environment named '%s'", invalidEnvironment))
Expand Down Expand Up @@ -109,16 +107,15 @@ func TestPantheonBackupLinks(t *testing.T) {
defer testcommon.Chdir(testDir)()

config, err := NewConfig(testDir, "pantheon")
// No config should exist so this will result in an error
assert.Error(err)
assert.NoError(err)
config.Name = pantheonTestSiteName

provider := PantheonProvider{}
err = provider.Init(config)
assert.NoError(err)

provider.Sitename = pantheonTestSiteName
provider.Environment = pantheonTestEnvName
provider.EnvironmentName = pantheonTestEnvName

// Ensure GetBackup triggers an error for unknown backup types.
_, _, err = provider.GetBackup(util.RandString(8))
Expand Down

0 comments on commit 4ca6d38

Please sign in to comment.