Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 81 additions & 31 deletions pkg/asset/installconfig/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package installconfig

import (
"fmt"
"net/url"
"sort"
"strings"

"github.com/AlecAivazis/survey"
Expand All @@ -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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like something that could be a pain to keep up to date. How about a TODO or something to consider getting the list from an AWS API?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The installer doesn't require Internet access today. Reading this list from AWS would introduce that dependency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a TODO or something to consider getting the list from an AWS API?

And also, AWS doesn't seem to provide an API for pulling the locations. Previous discussion starting here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thanks for the info and references

}
)

// Platform is an asset that queries the user for the platform on which to install
Expand Down Expand Up @@ -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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put a check here? Just in case the dynamic type cast fails.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is correct. Programs should panic if there is a programmer error (vs an input error).

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",
}
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this cast needed? regionTransform() returns a string.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this cast needed?

Yes.

regionTransform() returns a string.

No, regionTransform holds the result of TransformString, which is a Tranformer (and returns an interface{}). The string return is the function we're passing into TransformString as an argument, and we don't assign that function a name.

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",
}
Expand All @@ -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",
}
Expand Down
17 changes: 15 additions & 2 deletions pkg/asset/installconfig/ssh.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package installconfig

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -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),
Expand Down Expand Up @@ -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{{
Expand Down
53 changes: 38 additions & 15 deletions pkg/asset/installconfig/stock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package installconfig
import (
"github.com/AlecAivazis/survey"

"github.com/openshift/installer/installer/pkg/validate"
"github.com/openshift/installer/pkg/asset"
)

Expand Down Expand Up @@ -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",
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/asset/userprovided.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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{
Expand Down