diff --git a/pkg/cloudprovider/providers/vsphere/vsphere.go b/pkg/cloudprovider/providers/vsphere/vsphere.go index c9390077849..27b0d8444f7 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere.go @@ -61,6 +61,18 @@ var datastoreFolderIDMap = make(map[string]map[string]string) var cleanUpRoutineInitLock sync.Mutex var cleanUpDummyVMLock sync.RWMutex +// Error Messages +const ( + MissingUsernameErrMsg = "Username is missing" + MissingPasswordErrMsg = "Password is missing" +) + +// Error constants +var ( + ErrUsernameMissing = errors.New(MissingUsernameErrMsg) + ErrPasswordMissing = errors.New(MissingPasswordErrMsg) +) + // VSphere is an implementation of cloud provider Interface for VSphere. type VSphere struct { cfg *VSphereConfig @@ -68,8 +80,9 @@ type VSphere struct { // Maps the VSphere IP address to VSphereInstance vsphereInstanceMap map[string]*VSphereInstance // Responsible for managing discovery of k8s node, their location etc. - nodeManager *NodeManager - vmUUID string + nodeManager *NodeManager + vmUUID string + isSecretInfoProvided bool } // Represents a vSphere instance where one or more kubernetes nodes are running. @@ -131,6 +144,10 @@ type VSphereConfig struct { // Combining the WorkingDir and VMName can form a unique InstanceID. // When vm-name is set, no username/password is required on worker nodes. VMName string `gcfg:"vm-name"` + // Name of the secret were vCenter credentials are present. + SecretName string `gcfg:"secret-name"` + // Secret Namespace where secret will be present that has vCenter credentials. + SecretNamespace string `gcfg:"secret-namespace"` } VirtualCenter map[string]*VirtualCenterConfig @@ -217,6 +234,18 @@ func (vs *VSphere) SetInformers(informerFactory informers.SharedInformerFactory) return } + if vs.isSecretInfoProvided { + secretCredentialManager := &SecretCredentialManager{ + SecretName: vs.cfg.Global.SecretName, + SecretNamespace: vs.cfg.Global.SecretNamespace, + SecretLister: informerFactory.Core().V1().Secrets().Lister(), + Cache: &SecretCache{ + VirtualCenter: make(map[string]*Credential), + }, + } + vs.nodeManager.UpdateCredentialManager(secretCredentialManager) + } + // Only on controller node it is required to register listeners. // Register callbacks for node updates glog.V(4).Infof("Setting up node informers for vSphere Cloud Provider") @@ -226,6 +255,7 @@ func (vs *VSphere) SetInformers(informerFactory informers.SharedInformerFactory) DeleteFunc: vs.NodeDeleted, }) glog.V(4).Infof("Node informers in vSphere cloud provider initialized") + } // Creates new worker node interface and returns @@ -247,19 +277,40 @@ func newWorkerNode() (*VSphere, error) { func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance, error) { vsphereInstanceMap := make(map[string]*VSphereInstance) + isSecretInfoProvided := true + + if cfg.Global.SecretName == "" || cfg.Global.SecretNamespace == "" { + glog.Warningf("SecretName and/or SecretNamespace is not provided. " + + "VCP will use username and password from config file") + isSecretInfoProvided = false + } + + if isSecretInfoProvided { + if cfg.Global.User != "" { + glog.Warning("Global.User and Secret info provided. VCP will use secret to get credentials") + cfg.Global.User = "" + } + if cfg.Global.Password != "" { + glog.Warning("Global.Password and Secret info provided. VCP will use secret to get credentials") + cfg.Global.Password = "" + } + } // Check if the vsphere.conf is in old format. In this // format the cfg.VirtualCenter will be nil or empty. if cfg.VirtualCenter == nil || len(cfg.VirtualCenter) == 0 { glog.V(4).Infof("Config is not per virtual center and is in old format.") - if cfg.Global.User == "" { - glog.Error("Global.User is empty!") - return nil, errors.New("Global.User is empty!") - } - if cfg.Global.Password == "" { - glog.Error("Global.Password is empty!") - return nil, errors.New("Global.Password is empty!") + if !isSecretInfoProvided { + if cfg.Global.User == "" { + glog.Error("Global.User is empty!") + return nil, ErrUsernameMissing + } + if cfg.Global.Password == "" { + glog.Error("Global.Password is empty!") + return nil, ErrPasswordMissing + } } + if cfg.Global.WorkingDir == "" { glog.Error("Global.WorkingDir is empty!") return nil, errors.New("Global.WorkingDir is empty!") @@ -285,6 +336,8 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance RoundTripperCount: cfg.Global.RoundTripperCount, } + // Note: If secrets info is provided username and password will be populated + // once secret is created. vSphereConn := vclib.VSphereConnection{ Username: vcConfig.User, Password: vcConfig.Password, @@ -305,31 +358,44 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance glog.Error(msg) return nil, errors.New(msg) } + for vcServer, vcConfig := range cfg.VirtualCenter { glog.V(4).Infof("Initializing vc server %s", vcServer) if vcServer == "" { glog.Error("vsphere.conf does not have the VirtualCenter IP address specified") return nil, errors.New("vsphere.conf does not have the VirtualCenter IP address specified") } - if vcConfig.User == "" { - vcConfig.User = cfg.Global.User - } - if vcConfig.Password == "" { - vcConfig.Password = cfg.Global.Password - } - if vcConfig.User == "" { - msg := fmt.Sprintf("vcConfig.User is empty for vc %s!", vcServer) - glog.Error(msg) - return nil, errors.New(msg) - } - if vcConfig.Password == "" { - msg := fmt.Sprintf("vcConfig.Password is empty for vc %s!", vcServer) - glog.Error(msg) - return nil, errors.New(msg) + + if !isSecretInfoProvided { + if vcConfig.User == "" { + vcConfig.User = cfg.Global.User + if vcConfig.User == "" { + glog.Errorf("vcConfig.User is empty for vc %s!", vcServer) + return nil, ErrUsernameMissing + } + } + if vcConfig.Password == "" { + vcConfig.Password = cfg.Global.Password + if vcConfig.Password == "" { + glog.Errorf("vcConfig.Password is empty for vc %s!", vcServer) + return nil, ErrPasswordMissing + } + } + } else { + if vcConfig.User != "" { + glog.Warningf("vcConfig.User for server %s and Secret info provided. VCP will use secret to get credentials", vcServer) + vcConfig.User = "" + } + if vcConfig.Password != "" { + glog.Warningf("vcConfig.Password for server %s and Secret info provided. VCP will use secret to get credentials", vcServer) + vcConfig.Password = "" + } } + if vcConfig.VCenterPort == "" { vcConfig.VCenterPort = cfg.Global.VCenterPort } + if vcConfig.Datacenters == "" { if cfg.Global.Datacenters != "" { vcConfig.Datacenters = cfg.Global.Datacenters @@ -342,6 +408,8 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance vcConfig.RoundTripperCount = cfg.Global.RoundTripperCount } + // Note: If secrets info is provided username and password will be populated + // once secret is created. vSphereConn := vclib.VSphereConnection{ Username: vcConfig.User, Password: vcConfig.Password, @@ -365,7 +433,30 @@ var getVMUUID = GetVMUUID // Creates new Controller node interface and returns func newControllerNode(cfg VSphereConfig) (*VSphere, error) { - var err error + vs, err := buildVSphereFromConfig(cfg) + if err != nil { + return nil, err + } + vs.hostName, err = os.Hostname() + if err != nil { + glog.Errorf("Failed to get hostname. err: %+v", err) + return nil, err + } + vs.vmUUID, err = getVMUUID() + if err != nil { + glog.Errorf("Failed to get uuid. err: %+v", err) + return nil, err + } + runtime.SetFinalizer(vs, logout) + return vs, nil +} + +// Initializes vSphere from vSphere CloudProvider Configuration +func buildVSphereFromConfig(cfg VSphereConfig) (*VSphere, error) { + isSecretInfoProvided := false + if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" { + isSecretInfoProvided = true + } if cfg.Disk.SCSIControllerType == "" { cfg.Disk.SCSIControllerType = vclib.PVSCSIControllerType @@ -394,20 +485,9 @@ func newControllerNode(cfg VSphereConfig) (*VSphere, error) { nodeInfoMap: make(map[string]*NodeInfo), registeredNodes: make(map[string]*v1.Node), }, - cfg: &cfg, + isSecretInfoProvided: isSecretInfoProvided, + cfg: &cfg, } - - vs.hostName, err = os.Hostname() - if err != nil { - glog.Errorf("Failed to get hostname. err: %+v", err) - return nil, err - } - vs.vmUUID, err = getVMUUID() - if err != nil { - glog.Errorf("Failed to get uuid. err: %+v", err) - return nil, err - } - runtime.SetFinalizer(&vs, logout) return &vs, nil } @@ -480,7 +560,7 @@ func (vs *VSphere) getVSphereInstanceForServer(vcServer string, ctx context.Cont return nil, errors.New(fmt.Sprintf("Cannot find node %q in vsphere configuration map", vcServer)) } // Ensure client is logged in and session is valid - err := vsphereIns.conn.Connect(ctx) + err := vs.nodeManager.vcConnect(ctx, vsphereIns) if err != nil { glog.Errorf("failed connecting to vcServer %q with error %+v", vcServer, err) return nil, err @@ -519,7 +599,7 @@ func (vs *VSphere) NodeAddresses(ctx context.Context, nodeName k8stypes.NodeName return nil, err } // Ensure client is logged in and session is valid - err = vsi.conn.Connect(ctx) + err = vs.nodeManager.vcConnect(ctx, vsi) if err != nil { return nil, err } @@ -634,7 +714,7 @@ func (vs *VSphere) InstanceID(ctx context.Context, nodeName k8stypes.NodeName) ( return "", err } // Ensure client is logged in and session is valid - err = vsi.conn.Connect(ctx) + err = vs.nodeManager.vcConnect(ctx, vsi) if err != nil { return "", err } @@ -725,7 +805,7 @@ func (vs *VSphere) AttachDisk(vmDiskPath string, storagePolicyName string, nodeN return "", err } // Ensure client is logged in and session is valid - err = vsi.conn.Connect(ctx) + err = vs.nodeManager.vcConnect(ctx, vsi) if err != nil { return "", err } @@ -792,7 +872,7 @@ func (vs *VSphere) DetachDisk(volPath string, nodeName k8stypes.NodeName) error return err } // Ensure client is logged in and session is valid - err = vsi.conn.Connect(ctx) + err = vs.nodeManager.vcConnect(ctx, vsi) if err != nil { return err } @@ -847,7 +927,7 @@ func (vs *VSphere) DiskIsAttached(volPath string, nodeName k8stypes.NodeName) (b return false, err } // Ensure client is logged in and session is valid - err = vsi.conn.Connect(ctx) + err = vs.nodeManager.vcConnect(ctx, vsi) if err != nil { return false, err }