diff --git a/docs/dev/libvirt-howto.md b/docs/dev/libvirt-howto.md index e5f2392f8dd..35bd006a233 100644 --- a/docs/dev/libvirt-howto.md +++ b/docs/dev/libvirt-howto.md @@ -223,15 +223,6 @@ Set `TAGS` when building if you need `destroy cluster` support for libvirt; this TAGS=libvirt_destroy hack/build.sh ``` -To avoid being prompted repeatedly, you can set [environment variables](../user/environment-variables.md) to reflect your libvirt choices. For example, selecting libvirt, setting [our earlier name choices](#pick-names), and telling both the installer and the machine-API operator to contact `libvirtd` at [the usual libvirt IP](#firewall), you can use: - -```sh -export OPENSHIFT_INSTALL_PLATFORM=libvirt -export OPENSHIFT_INSTALL_BASE_DOMAIN=tt.testing -export OPENSHIFT_INSTALL_CLUSTER_NAME=test1 -export OPENSHIFT_INSTALL_LIBVIRT_URI=qemu+tcp://192.168.122.1/system -``` - ## Cleanup If you compiled with `libvirt_destroy`, you can use: diff --git a/docs/user/environment-variables.md b/docs/user/environment-variables.md deleted file mode 100644 index 5075fa4a087..00000000000 --- a/docs/user/environment-variables.md +++ /dev/null @@ -1,51 +0,0 @@ -# Environment Variables - -The installer accepts a number of environment variable that allow the interactive prompts to be bypassed. Setting any of the following environment variables to their corresponding value, will cause the installer to use that value instead of prompting. - -## General - -* `OPENSHIFT_INSTALL_BASE_DOMAIN`: - The base domain of the cluster. - All DNS records will be sub-domains of this base and will also include the cluster name. - - For AWS, this must be a previously-existing public Route 53 zone. You can check for any already in your account with: - - ```sh - aws route53 list-hosted-zones --query 'HostedZones[? !(Config.PrivateZone)].Name' --output text - ``` - -* `OPENSHIFT_INSTALL_CLUSTER_NAME`: - The name of the cluster. - This will be used when generating sub-domains. - - For libvirt, choose a name that is unique enough to be used as a prefix during cluster deletion. - For example, if you use `demo` as your cluster name, `openshift-install destroy cluster` may destroy all domains, networks, pools, and volumes that begin with `demo`. -* `OPENSHIFT_INSTALL_PLATFORM`: - The platform onto which the cluster will be installed. - Valid values are `aws` and `libvirt`. -* `OPENSHIFT_INSTALL_PULL_SECRET`: - The container registry pull secret for this cluster (e.g. `{"auths": {...}}`). - You can get this secret from [try.openshift.com](https://try.openshift.com). -* `OPENSHIFT_INSTALL_PULL_SECRET_PATH`: - As an alternative to `OPENSHIFT_INSTALL_PULL_SECRET`, you can configure this variable with a path containing your pull secret. -* `OPENSHIFT_INSTALL_SSH_PUB_KEY`: - The SSH public key used to access all nodes within the cluster (e.g. `ssh-rsa AAAA...`). - This is optional. -* `OPENSHIFT_INSTALL_SSH_PUB_KEY_PATH`: - As an alternative to `OPENSHIFT_INSTALL_SSH_PUB_KEY`, you can configure this variable with a path containing your SSH public key (e.g. `~/.ssh/id_rsa.pub`). - -## Platform-Specific - -* `AWS_PROFILE`: - The AWS profile that corresponds to value in `${HOME}/.aws/credentials`. If not provided, the default is "default". -* `OPENSHIFT_INSTALL_AWS_REGION`: - The AWS region to be used for installation. -* `OPENSHIFT_INSTALL_LIBVIRT_URI`: - The libvirt connection URI to be used. - This must be accessible from the running cluster. -* `OPENSHIFT_INSTALL_LIBVIRT_IMAGE`: - The URI for the OS image. - For example it might be a URI like `https://example.com/rhcos-qemu.qcow2` or a local file like `file:///tmp/redhat-coreos-maipo-47.78-qemu.qcow2`. - - **Warning**: you should only set this if you're testing RHCOS releases. - Most users should allow the installer to choose the OS image. diff --git a/docs/user/tips-and-tricks.md b/docs/user/tips-and-tricks.md new file mode 100644 index 00000000000..78c25d3df0a --- /dev/null +++ b/docs/user/tips-and-tricks.md @@ -0,0 +1,23 @@ +# Tips and Tricks + +## Reusing an Install Config + +By default, the installer prompts for the necessary information every time a cluster is created. While convenient for one-off cases, this can become tiring if many clusters are needed. The prompts can be bypassed by taking advantage of the installer's notion of [multiple-invocations]. + +Start by creating an install config and saving it in a cluster-agnostic location: + +```console +openshift-install create install-config --dir=initial +mv initial/install-config.yml . +rm -rf initial +``` + +Future clusters can then be created by copying that install config into the target directory and then invoking the installer: + +```console +mkdir cluster-0 +cp install-config.yml cluster-0/ +openshift-install create cluster --dir=cluster-0 +``` + +[multiple-invocations]: overview.md#multiple-invocations diff --git a/pkg/asset/installconfig/aws/aws.go b/pkg/asset/installconfig/aws/aws.go index 08f18a803b5..0eea93a0b24 100644 --- a/pkg/asset/installconfig/aws/aws.go +++ b/pkg/asset/installconfig/aws/aws.go @@ -2,7 +2,6 @@ package aws import ( - "encoding/json" "fmt" "os" "path/filepath" @@ -17,7 +16,6 @@ import ( "github.com/sirupsen/logrus" survey "gopkg.in/AlecAivazis/survey.v1" - "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/types/aws" ) @@ -93,9 +91,10 @@ func Platform() (*aws.Platform, error) { sort.Strings(longRegions) sort.Strings(shortRegions) - region, err := asset.GenerateUserProvidedAsset( - "AWS Region", - &survey.Question{ + + var region string + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Select{ Message: "Region", Help: "The AWS region to be used for installation.", @@ -112,51 +111,40 @@ func Platform() (*aws.Platform, error) { }), Transform: regionTransform, }, - "OPENSHIFT_INSTALL_AWS_REGION", - ) + }, ®ion) if err != nil { return nil, err } - userTags := map[string]string{} - if value, ok := os.LookupEnv("_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS"); ok { - if err := json.Unmarshal([]byte(value), &userTags); err != nil { - return nil, errors.Wrapf(err, "_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS contains invalid JSON: %s", value) - } - } - return &aws.Platform{ VPCCIDRBlock: defaultVPCCIDR, Region: region, - UserTags: userTags, }, nil } func getCredentials() error { - keyID, err := asset.GenerateUserProvidedAsset( - "AWS Access Key ID", - &survey.Question{ + var keyID string + err := survey.Ask([]*survey.Question{ + { Prompt: &survey.Input{ Message: "AWS Access Key ID", Help: "The AWS access key ID to use for installation (this is not your username).\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html", }, }, - "", - ) + }, keyID) if err != nil { return err } - secretKey, err := asset.GenerateUserProvidedAsset( - "AWS Access Key ID", - &survey.Question{ + var secretKey string + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Password{ Message: "AWS Secret Access Key", Help: "The AWS secret access key corresponding to your access key ID (this is not your password).", }, }, - "", - ) + }, &secretKey) if err != nil { return err } diff --git a/pkg/asset/installconfig/basedomain.go b/pkg/asset/installconfig/basedomain.go index 353748fee41..46c65472bca 100644 --- a/pkg/asset/installconfig/basedomain.go +++ b/pkg/asset/installconfig/basedomain.go @@ -20,9 +20,8 @@ func (a *baseDomain) Dependencies() []asset.Asset { // Generate queries for the base domain from the user. func (a *baseDomain) Generate(asset.Parents) error { - bd, err := asset.GenerateUserProvidedAsset( - a.Name(), - &survey.Question{ + return survey.Ask([]*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 and will also include the cluster name.\n\nFor AWS, this must be a previously-existing public Route 53 zone. You can check for any already in your account with:\n\n $ aws route53 list-hosted-zones --query 'HostedZones[? !(Config.PrivateZone)].Name' --output text", @@ -31,10 +30,7 @@ func (a *baseDomain) Generate(asset.Parents) error { return validate.DomainName(ans.(string)) }), }, - "OPENSHIFT_INSTALL_BASE_DOMAIN", - ) - a.BaseDomain = bd - return err + }, &a.BaseDomain) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/installconfig/clustername.go b/pkg/asset/installconfig/clustername.go index ab2788dba68..46680e1ccdf 100644 --- a/pkg/asset/installconfig/clustername.go +++ b/pkg/asset/installconfig/clustername.go @@ -20,9 +20,8 @@ func (a *clusterName) Dependencies() []asset.Asset { // Generate queries for the cluster name from the user. func (a *clusterName) Generate(asset.Parents) error { - n, err := asset.GenerateUserProvidedAsset( - a.Name(), - &survey.Question{ + return survey.Ask([]*survey.Question{ + { Prompt: &survey.Input{ Message: "Cluster Name", Help: "The name of the cluster. This will be used when generating sub-domains.\n\nFor libvirt, choose a name that is unique enough to be used as a prefix during cluster deletion. For example, if you use 'demo' as your cluster name, `openshift-install destroy cluster` may destroy all domains, networks, pools, and volumes that begin with 'demo'.", @@ -31,10 +30,7 @@ func (a *clusterName) Generate(asset.Parents) error { return validate.DomainName(ans.(string)) }), }, - "OPENSHIFT_INSTALL_CLUSTER_NAME", - ) - a.ClusterName = n - return err + }, &a.ClusterName) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/installconfig/emailaddress.go b/pkg/asset/installconfig/emailaddress.go index ff714b94c07..1cd131abaa9 100644 --- a/pkg/asset/installconfig/emailaddress.go +++ b/pkg/asset/installconfig/emailaddress.go @@ -20,9 +20,8 @@ func (a *emailAddress) Dependencies() []asset.Asset { // Generate queries for the email address from the user. func (a *emailAddress) Generate(asset.Parents) error { - email, err := asset.GenerateUserProvidedAsset( - a.Name(), - &survey.Question{ + return survey.Ask([]*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.", @@ -31,10 +30,7 @@ func (a *emailAddress) Generate(asset.Parents) error { return validate.Email(ans.(string)) }), }, - "OPENSHIFT_INSTALL_EMAIL_ADDRESS", - ) - a.EmailAddress = email - return err + }, &a.EmailAddress) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/installconfig/libvirt/libvirt.go b/pkg/asset/installconfig/libvirt/libvirt.go index 80cc4203c85..ce06a438cb4 100644 --- a/pkg/asset/installconfig/libvirt/libvirt.go +++ b/pkg/asset/installconfig/libvirt/libvirt.go @@ -5,12 +5,10 @@ import ( "context" "fmt" "net/url" - "os" "github.com/pkg/errors" survey "gopkg.in/AlecAivazis/survey.v1" - "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/rhcos" "github.com/openshift/installer/pkg/types/libvirt" ) @@ -22,9 +20,9 @@ const ( // Platform collects libvirt-specific configuration. func Platform() (*libvirt.Platform, error) { - uri, err := asset.GenerateUserProvidedAsset( - "Libvirt Connection URI", - &survey.Question{ + var uri string + err := survey.Ask([]*survey.Question{ + { Prompt: &survey.Input{ Message: "Libvirt Connection URI", Help: "The libvirt connection URI to be used. This must be accessible from the running cluster.", @@ -32,23 +30,14 @@ func Platform() (*libvirt.Platform, error) { }, Validate: survey.ComposeValidators(survey.Required, uriValidator), }, - "OPENSHIFT_INSTALL_LIBVIRT_URI", - ) + }, &uri) if err != nil { return nil, err } - qcowImage, ok := os.LookupEnv("OPENSHIFT_INSTALL_LIBVIRT_IMAGE") - if ok { - err = validURI(qcowImage) - if err != nil { - return nil, errors.Wrap(err, "resolve OPENSHIFT_INSTALL_LIBVIRT_IMAGE") - } - } else { - qcowImage, err = rhcos.QEMU(context.TODO(), rhcos.DefaultChannel) - if err != nil { - return nil, errors.Wrap(err, "failed to fetch QEMU image URL") - } + qcowImage, err := rhcos.QEMU(context.TODO(), rhcos.DefaultChannel) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch QEMU image URL") } return &libvirt.Platform{ diff --git a/pkg/asset/installconfig/openstack/openstack.go b/pkg/asset/installconfig/openstack/openstack.go index 0f1b39d51c8..2af3f81df1e 100644 --- a/pkg/asset/installconfig/openstack/openstack.go +++ b/pkg/asset/installconfig/openstack/openstack.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" survey "gopkg.in/AlecAivazis/survey.v1" - "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/types/openstack" ) @@ -133,9 +132,9 @@ func Platform() (*openstack.Platform, error) { if err != nil { return nil, err } - cloud, err := asset.GenerateUserProvidedAsset( - "OpenStack Cloud", - &survey.Question{ + var cloud string + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Select{ Message: "Cloud", Help: "The OpenStack cloud name from clouds.yaml.", @@ -150,8 +149,7 @@ func Platform() (*openstack.Platform, error) { return nil }), }, - "OPENSHIFT_INSTALL_OPENSTACK_CLOUD", - ) + }, &cloud) if err != nil { return nil, err } @@ -160,9 +158,9 @@ func Platform() (*openstack.Platform, error) { if err != nil { return nil, err } - region, err := asset.GenerateUserProvidedAsset( - "OpenStack Region", - &survey.Question{ + var region string + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Select{ Message: "Region", Help: "The OpenStack region to be used for installation.", @@ -178,8 +176,7 @@ func Platform() (*openstack.Platform, error) { return nil }), }, - "OPENSHIFT_INSTALL_OPENSTACK_REGION", - ) + }, ®ion) if err != nil { return nil, err } @@ -188,9 +185,9 @@ func Platform() (*openstack.Platform, error) { if err != nil { return nil, err } - image, err := asset.GenerateUserProvidedAsset( - "OpenStack Image", - &survey.Question{ + var image string + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Select{ Message: "Image", Help: "The OpenStack image name to be used for installation.", @@ -206,8 +203,7 @@ func Platform() (*openstack.Platform, error) { return nil }), }, - "OPENSHIFT_INSTALL_OPENSTACK_IMAGE", - ) + }, &image) if err != nil { return nil, err } @@ -216,9 +212,9 @@ func Platform() (*openstack.Platform, error) { if err != nil { return nil, err } - extNet, err := asset.GenerateUserProvidedAsset( - "OpenStack External Network", - &survey.Question{ + var extNet string + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Select{ Message: "ExternalNetwork", Help: "The OpenStack external network name to be used for installation.", @@ -233,8 +229,7 @@ func Platform() (*openstack.Platform, error) { return nil }), }, - "OPENSHIFT_INSTALL_OPENSTACK_EXTERNAL_NETWORK", - ) + }, &extNet) if err != nil { return nil, errors.Wrapf(err, "failed to Marshal %s platform", openstack.Name) } diff --git a/pkg/asset/installconfig/platform.go b/pkg/asset/installconfig/platform.go index 86c93c1cfa5..f2e6974e24d 100644 --- a/pkg/asset/installconfig/platform.go +++ b/pkg/asset/installconfig/platform.go @@ -63,10 +63,9 @@ func (a *platform) Name() string { return "Platform" } -func (a *platform) queryUserForPlatform() (string, error) { - return asset.GenerateUserProvidedAsset( - "Platform", - &survey.Question{ +func (a *platform) queryUserForPlatform() (platform string, err error) { + err = survey.Ask([]*survey.Question{ + { Prompt: &survey.Select{ Message: "Platform", Options: types.PlatformNames, @@ -80,6 +79,6 @@ func (a *platform) queryUserForPlatform() (string, error) { return nil }), }, - "OPENSHIFT_INSTALL_PLATFORM", - ) + }, &platform) + return } diff --git a/pkg/asset/installconfig/pullsecret.go b/pkg/asset/installconfig/pullsecret.go index bcda82b6147..60f002c352f 100644 --- a/pkg/asset/installconfig/pullsecret.go +++ b/pkg/asset/installconfig/pullsecret.go @@ -20,9 +20,8 @@ func (a *pullSecret) Dependencies() []asset.Asset { // Generate queries for the pull secret from the user. func (a *pullSecret) Generate(asset.Parents) error { - s, err := asset.GenerateUserProvidedAssetForPath( - a.Name(), - &survey.Question{ + return survey.Ask([]*survey.Question{ + { Prompt: &survey.Input{ Message: "Pull Secret", Help: "The container registry pull secret for this cluster, as a single line of JSON (e.g. {\"auths\": {...}}).\n\nYou can get this secret from https://try.openshift.com", @@ -31,11 +30,7 @@ func (a *pullSecret) Generate(asset.Parents) error { return validate.JSON([]byte(ans.(string))) }), }, - "OPENSHIFT_INSTALL_PULL_SECRET", - "OPENSHIFT_INSTALL_PULL_SECRET_PATH", - ) - a.PullSecret = s - return err + }, &a.PullSecret) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/installconfig/ssh.go b/pkg/asset/installconfig/ssh.go index d792e402a61..0e57cc7ab9d 100644 --- a/pkg/asset/installconfig/ssh.go +++ b/pkg/asset/installconfig/ssh.go @@ -47,38 +47,21 @@ func readSSHKey(path string) (string, error) { // Generate generates the SSH public key asset. func (a *sshPublicKey) Generate(asset.Parents) error { - if value, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY"); ok { - if value != "" { - if err := validate.SSHPublicKey(value); err != nil { - return errors.Wrap(err, "failed to validate public key") - } - } - a.Key = value - return nil + pubKeys := map[string]string{ + none: "", } - - pubKeys := map[string]string{} - if path, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY_PATH"); ok { - key, err := readSSHKey(path) + home := os.Getenv("HOME") + if home != "" { + paths, err := filepath.Glob(filepath.Join(home, ".ssh", "*.pub")) if err != nil { - return errors.Wrap(err, "failed to read public key file") + return errors.Wrap(err, "failed to glob for public key files") } - pubKeys[path] = key - } else { - pubKeys[none] = "" - home := os.Getenv("HOME") - if home != "" { - paths, err := filepath.Glob(filepath.Join(home, ".ssh", "*.pub")) + for _, path := range paths { + key, err := readSSHKey(path) if err != nil { - return errors.Wrap(err, "failed to glob for public key files") - } - for _, path := range paths { - key, err := readSSHKey(path) - if err != nil { - continue - } - pubKeys[path] = key + continue } + pubKeys[path] = key } } diff --git a/pkg/asset/userprovided.go b/pkg/asset/userprovided.go deleted file mode 100644 index 0251982125b..00000000000 --- a/pkg/asset/userprovided.go +++ /dev/null @@ -1,50 +0,0 @@ -package asset - -import ( - "io/ioutil" - "os" - - "github.com/pkg/errors" - survey "gopkg.in/AlecAivazis/survey.v1" -) - -// GenerateUserProvidedAsset queries for input from the user. -func GenerateUserProvidedAsset(inputName string, question *survey.Question, envVarName string) (string, error) { - return generateUserProvidedAsset(inputName, question, envVarName, "") -} - -// GenerateUserProvidedAssetForPath queries for input from the user. The input can -// be read from a file specified in an environment variable. -func GenerateUserProvidedAssetForPath(inputName string, question *survey.Question, envVarName, pathEnvVarName string) (string, error) { - return generateUserProvidedAsset(inputName, question, envVarName, pathEnvVarName) -} - -func generateUserProvidedAsset(inputName string, question *survey.Question, envVarName, pathEnvVarName string) (response string, err error) { - defer func() { - if err != nil { - err = errors.Wrapf(err, "failed to acquire user-provided input %s", inputName) - } - }() - - if value, ok := os.LookupEnv(envVarName); ok { - response = value - } else if path, ok := os.LookupEnv(pathEnvVarName); ok { - value, err := ioutil.ReadFile(path) - if err != nil { - return "", errors.Wrapf(err, "failed to read file from %s", pathEnvVarName) - } - response = string(value) - } - - if response == "" { - if err := survey.Ask([]*survey.Question{question}, &response); err != nil { - return "", errors.Wrap(err, "failed to Ask") - } - } else if question.Validate != nil { - if err := question.Validate(response); err != nil { - return "", errors.Wrap(err, "validation failed") - } - } - - return response, nil -}