diff --git a/cmd/syncer/main.go b/cmd/syncer/main.go index cf802b653b..eaea62e288 100644 --- a/cmd/syncer/main.go +++ b/cmd/syncer/main.go @@ -38,6 +38,7 @@ import ( "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/node" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config" + "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/env" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/prometheus" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/utils" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common" @@ -96,7 +97,11 @@ func main() { fmt.Printf("%s\n", syncer.Version) return } - logType := logger.LogLevel(os.Getenv(logger.EnvLoggerLevel)) + + // Load startup environment variables + startupEnv := env.Load(context.Background()) + + logType := logger.LogLevel(startupEnv.LoggerLevel) logger.SetLoggerLevel(logType) ctx, log := logger.GetNewContextWithLogger() log.Infof("Version : %s", syncer.Version) @@ -473,8 +478,10 @@ func sanitizeName(name string) string { // the env var POD_NAMESPACE, then the file /var/run/secrets/kubernetes.io/serviceaccount/namespace. // if neither returns a valid namespace, the "default" namespace is returned func inClusterNamespace() string { - if ns := os.Getenv("POD_NAMESPACE"); ns != "" { - return ns + // Try to get from centralized env first + startupEnv := env.GetStartupEnv() + if startupEnv.PodNamespace != "" { + return startupEnv.PodNamespace } if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { diff --git a/cmd/vsphere-csi/main.go b/cmd/vsphere-csi/main.go index f0b1a2507b..c88fd6af2f 100644 --- a/cmd/vsphere-csi/main.go +++ b/cmd/vsphere-csi/main.go @@ -27,11 +27,11 @@ import ( "syscall" csiconfig "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config" + "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/env" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/utils" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger" - csitypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/types" ) var ( @@ -53,7 +53,11 @@ func main() { fmt.Printf("%s\n", service.Version) return } - logType := logger.LogLevel(os.Getenv(logger.EnvLoggerLevel)) + + // Load startup environment variables + startupEnv := env.Load(context.Background()) + + logType := logger.LogLevel(startupEnv.LoggerLevel) logger.SetLoggerLevel(logType) ctx, log := logger.GetNewContextWithLogger() log.Infof("Version : %s", service.Version) @@ -73,13 +77,11 @@ func main() { if err != nil { log.Errorf("failed retrieving the cluster flavor. Error: %v", err) } - serviceMode := os.Getenv(csitypes.EnvVarMode) commonco.SetInitParams(ctx, clusterFlavor, &service.COInitParams, *supervisorFSSName, *supervisorFSSNamespace, - *internalFSSName, *internalFSSNamespace, serviceMode, "") + *internalFSSName, *internalFSSNamespace, startupEnv.CSIMode, "") // If no endpoint is set then exit the program. - CSIEndpoint := os.Getenv(csitypes.EnvVarEndpoint) - if CSIEndpoint == "" { + if startupEnv.CSIEndpoint == "" { log.Error("CSI endpoint cannot be empty. Please set the env variable.") os.Exit(1) } @@ -106,7 +108,7 @@ func main() { }() vSphereCSIDriver := service.NewDriver() - vSphereCSIDriver.Run(ctx, CSIEndpoint) + vSphereCSIDriver.Run(ctx, startupEnv.CSIEndpoint) } diff --git a/pkg/common/env/env.go b/pkg/common/env/env.go new file mode 100644 index 0000000000..7a02159c4d --- /dev/null +++ b/pkg/common/env/env.go @@ -0,0 +1,91 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package env + +import ( + "context" + "os" + + "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger" +) + +// StartupEnv holds all environment variables read at service startup. +type StartupEnv struct { + // LoggerLevel specifies the logging level (PRODUCTION or DEVELOPMENT) + LoggerLevel string + + // CSIEndpoint specifies the CSI endpoint for CSI driver + CSIEndpoint string + + // CSIMode specifies the service mode (controller or node) + CSIMode string + + // ClusterFlavor specifies the cluster flavor (VANILLA, WORKLOAD, or GUEST) + ClusterFlavor string + + // PodNamespace specifies the namespace where the pod is running + PodNamespace string +} + +var ( + // globalStartupEnv holds the loaded startup environment variables + globalStartupEnv StartupEnv +) + +// Load reads and validates all startup environment variables. +// This should be called once at service initialization. +func Load(ctx context.Context) StartupEnv { + log := logger.GetLogger(ctx) + + env := StartupEnv{ + LoggerLevel: os.Getenv("LOGGER_LEVEL"), + CSIEndpoint: os.Getenv("CSI_ENDPOINT"), + CSIMode: os.Getenv("X_CSI_MODE"), + ClusterFlavor: os.Getenv("CLUSTER_FLAVOR"), + PodNamespace: os.Getenv("POD_NAMESPACE"), + } + + // Validate required environment variables for CSI driver + // Note: CSI_ENDPOINT is required for vsphere-csi but not for syncer + // The validation is handled by the caller based on service type + + // Store globally for access by other components + globalStartupEnv = env + + // Log loaded configuration (without sensitive values) + log.Infof("Loaded startup environment: LoggerLevel=%q, CSIEndpoint=%q, CSIMode=%q, ClusterFlavor=%q, PodNamespace=%q", + env.LoggerLevel, maskValue(env.CSIEndpoint), env.CSIMode, env.ClusterFlavor, env.PodNamespace) + + return env +} + +// GetStartupEnv returns the globally loaded startup environment. +// Returns an error if Load has not been called yet. +func GetStartupEnv() StartupEnv { + return globalStartupEnv +} + +// maskValue masks a value for logging, showing only the first few characters. +func maskValue(value string) string { + if value == "" { + return "" + } + if len(value) <= 10 { + return "***" + } + return value[:10] + "***" +} diff --git a/pkg/csi/service/driver.go b/pkg/csi/service/driver.go index a2b7e8620d..c0ff0ed019 100644 --- a/pkg/csi/service/driver.go +++ b/pkg/csi/service/driver.go @@ -27,6 +27,7 @@ import ( "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/node" cnsconfig "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config" + "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/env" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger" @@ -74,7 +75,14 @@ type vsphereCSIDriver struct { // If k8s node died unexpectedly in an earlier run, the unix socket is left // behind. This method will clean up the sock file during initialization. func init() { - sockPath := os.Getenv(csitypes.EnvVarEndpoint) + // Try to get from centralized env, fallback to direct os.Getenv for backward compatibility + var sockPath string + startupEnv := env.GetStartupEnv() + if startupEnv.CSIEndpoint != "" { + sockPath = startupEnv.CSIEndpoint + } else { + sockPath = os.Getenv(csitypes.EnvVarEndpoint) + } sockPath = strings.TrimPrefix(sockPath, UnixSocketPrefix) if len(sockPath) > 1 { // Minimal valid path length. os.Remove(sockPath) @@ -90,7 +98,15 @@ func NewDriver() Driver { func (driver *vsphereCSIDriver) GetController() csi.ControllerServer { // Check which controller type to use. - clusterFlavor = cnstypes.CnsClusterFlavor(os.Getenv(cnsconfig.EnvClusterFlavor)) + // Try to get from centralized env, fallback to direct os.Getenv for backward compatibility + var clusterFlavorStr string + startupEnv := env.GetStartupEnv() + if startupEnv.ClusterFlavor != "" { + clusterFlavorStr = startupEnv.ClusterFlavor + } else { + clusterFlavorStr = os.Getenv(cnsconfig.EnvClusterFlavor) + } + clusterFlavor = cnstypes.CnsClusterFlavor(clusterFlavorStr) switch clusterFlavor { case cnstypes.CnsClusterFlavorWorkload: driver.cnscs = wcp.New() @@ -130,7 +146,13 @@ func (driver *vsphereCSIDriver) BeforeServe(ctx context.Context) error { } // Get the SP's operating mode. - driver.mode = os.Getenv(csitypes.EnvVarMode) + // Try to get from centralized env, fallback to direct os.Getenv for backward compatibility + startupEnv := env.GetStartupEnv() + if startupEnv.CSIMode != "" { + driver.mode = startupEnv.CSIMode + } else { + driver.mode = os.Getenv(csitypes.EnvVarMode) + } // Create OsUtils for node driver driver.osUtils, err = osutils.NewOsUtils(ctx) if err != nil { diff --git a/pkg/csi/service/server.go b/pkg/csi/service/server.go index f8f2284b19..50a9e9e970 100644 --- a/pkg/csi/service/server.go +++ b/pkg/csi/service/server.go @@ -24,6 +24,7 @@ import ( "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc" + "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/env" "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger" csitypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/types" @@ -131,7 +132,14 @@ func (s *nonBlockingGRPCServer) serve(endpoint string, ids csi.IdentityServer, log.Info("identity service registered") // Determine which of the controller/node services to register. - mode := os.Getenv(csitypes.EnvVarMode) + // Try to get from centralized env, fallback to direct os.Getenv for backward compatibility + var mode string + startupEnv := env.GetStartupEnv() + if startupEnv.CSIMode != "" { + mode = startupEnv.CSIMode + } else { + mode = os.Getenv(csitypes.EnvVarMode) + } if strings.EqualFold(mode, "controller") { if cs == nil { return logger.LogNewError(log, "controller service required when running in controller mode")