diff --git a/pkg/asset/installconfig/openstack/openstack.go b/pkg/asset/installconfig/openstack/openstack.go index 621cdce3c33..ee9d3f9000f 100644 --- a/pkg/asset/installconfig/openstack/openstack.go +++ b/pkg/asset/installconfig/openstack/openstack.go @@ -98,6 +98,33 @@ func Platform() (*openstack.Platform, error) { return nil, err } + floatingIPNames, err := validValuesFetcher.GetFloatingIPNames(cloud, extNet) + if err != nil { + return nil, err + } + sort.Strings(floatingIPNames) + var lbFloatingIP string + err = survey.Ask([]*survey.Question{ + { + Prompt: &survey.Select{ + Message: "APIFloatingIPAddress", + Help: "The Floating IP address used for external access to the OpenShift API.", + Options: floatingIPNames, + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + value := ans.(string) + i := sort.SearchStrings(floatingIPNames, value) + if i == len(floatingIPNames) || floatingIPNames[i] != value { + return errors.Errorf("invalid floating IP %q, should be one of %+v", value, strings.Join(floatingIPNames, ", ")) + } + return nil + }), + }, + }, &lbFloatingIP) + if err != nil { + return nil, err + } + flavorNames, err := validValuesFetcher.GetFlavorNames(cloud) if err != nil { return nil, err @@ -156,6 +183,7 @@ func Platform() (*openstack.Platform, error) { Cloud: cloud, ExternalNetwork: extNet, FlavorName: flavor, + LbFloatingIP: lbFloatingIP, TrunkSupport: trunkSupport, OctaviaSupport: octaviaSupport, }, nil diff --git a/pkg/types/openstack/validation/mock/validvaluesfetcher_generated.go b/pkg/types/openstack/validation/mock/validvaluesfetcher_generated.go index 234e9fd61b5..e1e2780c3cb 100644 --- a/pkg/types/openstack/validation/mock/validvaluesfetcher_generated.go +++ b/pkg/types/openstack/validation/mock/validvaluesfetcher_generated.go @@ -121,3 +121,18 @@ func (mr *MockValidValuesFetcherMockRecorder) GetServiceCatalog(cloud interface{ mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceCatalog", reflect.TypeOf((*MockValidValuesFetcher)(nil).GetServiceCatalog), cloud) } + +// GetFloatingIPNames mocks base method +func (m *MockValidValuesFetcher) GetFloatingIPNames(cloud, floatingNetwork string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFloatingIPNames", cloud, floatingNetwork) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFloatingIPNames indicates an expected call of GetFloatingIPNames +func (mr *MockValidValuesFetcherMockRecorder) GetFloatingIPNames(cloud, floatingNetwork interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFloatingIPNames", reflect.TypeOf((*MockValidValuesFetcher)(nil).GetFloatingIPNames), cloud, floatingNetwork) +} diff --git a/pkg/types/openstack/validation/realvalidvaluesfetcher.go b/pkg/types/openstack/validation/realvalidvaluesfetcher.go index e73a1ab9fae..ec85250cfaa 100644 --- a/pkg/types/openstack/validation/realvalidvaluesfetcher.go +++ b/pkg/types/openstack/validation/realvalidvaluesfetcher.go @@ -1,11 +1,14 @@ package validation import ( + "github.com/pkg/errors" + "github.com/gophercloud/gophercloud/openstack/common/extensions" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/openstack/identity/v3/regions" "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" netext "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/utils/openstack/clientconfig" ) @@ -194,3 +197,47 @@ func (f realValidValuesFetcher) GetServiceCatalog(cloud string) ([]string, error return serviceCatalogNames, nil } + +func (f realValidValuesFetcher) GetFloatingIPNames(cloud string, floatingNetworkName string) ([]string, error) { + opts := &clientconfig.ClientOpts{ + Cloud: cloud, + } + + conn, err := clientconfig.NewServiceClient("network", opts) + if err != nil { + return nil, err + } + + // floatingips.ListOpts requires an ID so we must get it from the name + floatingNetworkID, err := networks.IDFromName(conn, floatingNetworkName) + if err != nil { + return nil, err + } + + // Only show IPs that belong to the network and are not in use + listOpts := floatingips.ListOpts{ + FloatingNetworkID: floatingNetworkID, + Status: "DOWN", + } + + allPages, err := floatingips.List(conn, listOpts).AllPages() + if err != nil { + return nil, err + } + + allFloatingIPs, err := floatingips.ExtractFloatingIPs(allPages) + if err != nil { + return nil, err + } + + if len(allFloatingIPs) == 0 { + return nil, errors.New("there are no unassigned floating IP addresses available") + } + + floatingIPNames := make([]string, len(allFloatingIPs)) + for i, floatingIP := range allFloatingIPs { + floatingIPNames[i] = floatingIP.FloatingIP + } + + return floatingIPNames, nil +} diff --git a/pkg/types/openstack/validation/validvaluesfetcher.go b/pkg/types/openstack/validation/validvaluesfetcher.go index 59983505d43..5ed26d16710 100644 --- a/pkg/types/openstack/validation/validvaluesfetcher.go +++ b/pkg/types/openstack/validation/validvaluesfetcher.go @@ -16,4 +16,6 @@ type ValidValuesFetcher interface { GetNetworkExtensionsAliases(cloud string) ([]string, error) // GetServiceCatalog gets the catalog service names GetServiceCatalog(cloud string) ([]string, error) + // GetFloatingIPNames gets the floating IPs + GetFloatingIPNames(cloud string, floatingNetwork string) ([]string, error) }