diff --git a/pkg/asset/installconfig/platform.go b/pkg/asset/installconfig/platform.go index a1b1ad99171..9901324c6b0 100644 --- a/pkg/asset/installconfig/platform.go +++ b/pkg/asset/installconfig/platform.go @@ -2,6 +2,8 @@ package installconfig import ( "fmt" + "net/url" + "sort" "strings" "github.com/AlecAivazis/survey" @@ -18,6 +20,27 @@ const ( var ( validPlatforms = []string{AWSPlatformType, LibvirtPlatformType} + + validAWSRegions = map[string]string{ + "ap-northeast-1": "Tokyo", + "ap-northeast-2": "Seoul", + "ap-northeast-3": "Osaka-Local", + "ap-south-1": "Mumbai", + "ap-southeast-1": "Singapore", + "ap-southeast-2": "Sydney", + "ca-central-1": "Central", + "cn-north-1": "Beijing", + "cn-northwest-1": "Ningxia", + "eu-central-1": "Frankfurt", + "eu-west-1": "Ireland", + "eu-west-2": "London", + "eu-west-3": "Paris", + "sa-east-1": "São Paulo", + "us-east-1": "N. Virginia", + "us-east-2": "Ohio", + "us-west-1": "N. California", + "us-west-2": "Oregon", + } ) // Platform is an asset that queries the user for the platform on which to install @@ -62,10 +85,21 @@ func (a *Platform) Name() string { } func (a *Platform) queryUserForPlatform() (string, error) { + sort.Strings(validPlatforms) prompt := asset.UserProvided{ - Prompt: &survey.Select{ - Message: "Platform", - Options: validPlatforms, + Question: &survey.Question{ + Prompt: &survey.Select{ + Message: "Platform", + Options: validPlatforms, + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + choice := ans.(string) + i := sort.SearchStrings(validPlatforms, choice) + if i == len(validPlatforms) || validPlatforms[i] != choice { + return fmt.Errorf("invalid platform %q", choice) + } + return nil + }), }, EnvVarName: "OPENSHIFT_INSTALL_PLATFORM", } @@ -79,31 +113,34 @@ func (a *Platform) queryUserForPlatform() (string, error) { } func (a *Platform) awsPlatform() (*asset.State, error) { + longRegions := make([]string, 0, len(validAWSRegions)) + shortRegions := make([]string, 0, len(validAWSRegions)) + for id, location := range validAWSRegions { + longRegions = append(longRegions, fmt.Sprintf("%s (%s)", id, location)) + shortRegions = append(shortRegions, id) + } + regionTransform := survey.TransformString(func(s string) string { + return strings.SplitN(s, " ", 2)[0] + }) + sort.Strings(longRegions) + sort.Strings(shortRegions) prompt := asset.UserProvided{ - Prompt: &survey.Select{ - Message: "Region", - Help: "The AWS region to be used for installation.", - Default: "us-east-1 (N. Virginia)", - Options: []string{ - "us-east-2 (Ohio)", - "us-east-1 (N. Virginia)", - "us-west-1 (N. California)", - "us-west-2 (Oregon)", - "ap-south-1 (Mumbai)", - "ap-northeast-2 (Seoul)", - "ap-northeast-3 (Osaka-Local)", - "ap-southeast-1 (Singapore)", - "ap-southeast-2 (Sydney)", - "ap-northeast-1 (Tokyo)", - "ca-central-1 (Central)", - "cn-north-1 (Beijing)", - "cn-northwest-1 (Ningxia)", - "eu-central-1 (Frankfurt)", - "eu-west-1 (Ireland)", - "eu-west-2 (London)", - "eu-west-3 (Paris)", - "sa-east-1 (São Paulo)", + Question: &survey.Question{ + Prompt: &survey.Select{ + Message: "Region", + Help: "The AWS region to be used for installation.", + Default: "us-east-1 (N. Virginia)", + Options: longRegions, }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + choice := regionTransform(ans).(string) + i := sort.SearchStrings(shortRegions, choice) + if i == len(shortRegions) || shortRegions[i] != choice { + return fmt.Errorf("invalid region %q", choice) + } + return nil + }), + Transform: regionTransform, }, EnvVarName: "OPENSHIFT_INSTALL_AWS_REGION", } @@ -114,16 +151,29 @@ func (a *Platform) awsPlatform() (*asset.State, error) { return assetStateForStringContents( AWSPlatformType, - strings.Split(string(region.Contents[0].Data), " ")[0], + string(region.Contents[0].Data), ), nil } func (a *Platform) libvirtPlatform() (*asset.State, error) { prompt := asset.UserProvided{ - Prompt: &survey.Input{ - Message: "URI", - Help: "The libvirt connection URI to be used. This must be accessible from the running cluster.", - Default: "qemu+tcp://192.168.122.1/system", + Question: &survey.Question{ + Prompt: &survey.Input{ + Message: "URI", + Help: "The libvirt connection URI to be used. This must be accessible from the running cluster.", + Default: "qemu+tcp://192.168.122.1/system", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + value := ans.(string) + uri, err := url.Parse(value) + if err != nil { + return err + } + if uri.Scheme == "" { + return fmt.Errorf("invalid URI %q (no scheme)", value) + } + return nil + }), }, EnvVarName: "OPENSHIFT_INSTALL_LIBVIRT_URI", } diff --git a/pkg/asset/installconfig/ssh.go b/pkg/asset/installconfig/ssh.go index 0dece7fc378..950ecaa1d15 100644 --- a/pkg/asset/installconfig/ssh.go +++ b/pkg/asset/installconfig/ssh.go @@ -1,6 +1,7 @@ package installconfig import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -26,6 +27,11 @@ func (a *sshPublicKey) Dependencies() []asset.Asset { // Generate generates the SSH public key asset. func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.State, err error) { if value, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY"); ok { + if value != "" { + if err := validate.OpenSSHPublicKey(value); err != nil { + return nil, err + } + } return &asset.State{ Contents: []asset.Content{{ Data: []byte(value), @@ -68,10 +74,17 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat var path string survey.AskOne(&survey.Select{ Message: "SSH Public Key", - Help: "The SSH key used to access all nodes within the cluster. This is optional.", + Help: "The SSH public key used to access all nodes within the cluster. This is optional.", Options: paths, Default: none, - }, &path, nil) + }, &path, func(ans interface{}) error { + choice := ans.(string) + i := sort.SearchStrings(paths, choice) + if i == len(paths) || paths[i] != choice { + return fmt.Errorf("invalid path %q", choice) + } + return nil + }) return &asset.State{ Contents: []asset.Content{{ diff --git a/pkg/asset/installconfig/stock.go b/pkg/asset/installconfig/stock.go index e75aa35b862..bf8f64f8206 100644 --- a/pkg/asset/installconfig/stock.go +++ b/pkg/asset/installconfig/stock.go @@ -3,6 +3,7 @@ package installconfig import ( "github.com/AlecAivazis/survey" + "github.com/openshift/installer/installer/pkg/validate" "github.com/openshift/installer/pkg/asset" ) @@ -51,41 +52,63 @@ func (s *StockImpl) EstablishStock() { s.clusterID = &clusterID{} s.emailAddress = &asset.UserProvided{ AssetName: "Email Address", - Prompt: &survey.Input{ - Message: "Email Address", - Help: "The email address of the cluster administrator. This will be used to log in to the console.", + Question: &survey.Question{ + Prompt: &survey.Input{ + Message: "Email Address", + Help: "The email address of the cluster administrator. This will be used to log in to the console.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.Email(ans.(string)) + }), }, EnvVarName: "OPENSHIFT_INSTALL_EMAIL_ADDRESS", } s.password = &asset.UserProvided{ AssetName: "Password", - Prompt: &survey.Password{ - Message: "Password", - Help: "The password of the cluster administrator. This will be used to log in to the console.", + Question: &survey.Question{ + Prompt: &survey.Password{ + Message: "Password", + Help: "The password of the cluster administrator. This will be used to log in to the console.", + }, }, EnvVarName: "OPENSHIFT_INSTALL_PASSWORD", } s.baseDomain = &asset.UserProvided{ AssetName: "Base Domain", - Prompt: &survey.Input{ - Message: "Base Domain", - Help: "The base domain of the cluster. All DNS records will be sub-domains of this base.", + Question: &survey.Question{ + Prompt: &survey.Input{ + Message: "Base Domain", + Help: "The base domain of the cluster. All DNS records will be sub-domains of this base.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.DomainName(ans.(string)) + }), }, EnvVarName: "OPENSHIFT_INSTALL_BASE_DOMAIN", } s.clusterName = &asset.UserProvided{ AssetName: "Cluster Name", - Prompt: &survey.Input{ - Message: "Cluster Name", - Help: "The name of the cluster. This will be used when generating sub-domains.", + Question: &survey.Question{ + Prompt: &survey.Input{ + Message: "Cluster Name", + Help: "The name of the cluster. This will be used when generating sub-domains.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.DomainName(ans.(string)) + }), }, EnvVarName: "OPENSHIFT_INSTALL_CLUSTER_NAME", } s.pullSecret = &asset.UserProvided{ AssetName: "Pull Secret", - Prompt: &survey.Input{ - Message: "Pull Secret", - Help: "The container registry pull secret for this cluster.", + Question: &survey.Question{ + Prompt: &survey.Input{ + Message: "Pull Secret", + Help: "The container registry pull secret for this cluster.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.JSON([]byte(ans.(string))) + }), }, EnvVarName: "OPENSHIFT_INSTALL_PULL_SECRET", } diff --git a/pkg/asset/userprovided.go b/pkg/asset/userprovided.go index add4abd6768..68cdf8d7b4c 100644 --- a/pkg/asset/userprovided.go +++ b/pkg/asset/userprovided.go @@ -9,7 +9,7 @@ import ( // UserProvided generates an asset that is supplied by a user. type UserProvided struct { AssetName string - Prompt survey.Prompt + Question *survey.Question EnvVarName string } @@ -25,8 +25,13 @@ func (a *UserProvided) Generate(map[Asset]*State) (*State, error) { var response string if value, ok := os.LookupEnv(a.EnvVarName); ok { response = value + if a.Question.Validate != nil { + if err := a.Question.Validate(response); err != nil { + return nil, err + } + } } else { - survey.AskOne(a.Prompt, &response, survey.Required) + survey.AskOne(a.Question.Prompt, &response, a.Question.Validate) } return &State{