github.com/snowflakedb/gosnowflake@v1.9.0/client_configuration.go (about) 1 // Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. 2 3 package gosnowflake 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "os" 10 "path" 11 "path/filepath" 12 "runtime" 13 "strings" 14 ) 15 16 // log levels for easy logging 17 const ( 18 levelOff string = "OFF" // log level for logging switched off 19 levelError string = "ERROR" // error log level 20 levelWarn string = "WARN" // warn log level 21 levelInfo string = "INFO" // info log level 22 levelDebug string = "DEBUG" // debug log level 23 levelTrace string = "TRACE" // trace log level 24 ) 25 26 const ( 27 defaultConfigName = "sf_client_config.json" 28 clientConfEnvName = "SF_CLIENT_CONFIG_FILE" 29 ) 30 31 func getClientConfig(filePathFromConnectionString string) (*ClientConfig, string, error) { 32 configPredefinedDirPaths := clientConfigPredefinedDirs() 33 filePath, err := findClientConfigFilePath(filePathFromConnectionString, configPredefinedDirPaths) 34 if err != nil { 35 return nil, "", err 36 } 37 if filePath == "" { // we did not find a config file 38 return nil, "", nil 39 } 40 config, err := parseClientConfiguration(filePath) 41 return config, filePath, err 42 } 43 44 func findClientConfigFilePath(filePathFromConnectionString string, configPredefinedDirs []string) (string, error) { 45 if filePathFromConnectionString != "" { 46 logger.Infof("Using client configuration path from a connection string: %s", filePathFromConnectionString) 47 return filePathFromConnectionString, nil 48 } 49 envConfigFilePath := os.Getenv(clientConfEnvName) 50 if envConfigFilePath != "" { 51 logger.Infof("Using client configuration path from an environment variable: %s", envConfigFilePath) 52 return envConfigFilePath, nil 53 } 54 return searchForConfigFile(configPredefinedDirs) 55 } 56 57 func searchForConfigFile(directories []string) (string, error) { 58 for _, dir := range directories { 59 filePath := path.Join(dir, defaultConfigName) 60 exists, err := existsFile(filePath) 61 if err != nil { 62 return "", fmt.Errorf("error while searching for client config in directory: %s, err: %s", dir, err) 63 } 64 if exists { 65 logger.Infof("Using client configuration from a default directory: %s", filePath) 66 return filePath, nil 67 } 68 logger.Debugf("No client config found in directory: %s", dir) 69 } 70 logger.Info("No client config file found in default directories") 71 return "", nil 72 } 73 74 func existsFile(filePath string) (bool, error) { 75 _, err := os.Stat(filePath) 76 if err == nil { 77 return true, nil 78 } 79 if errors.Is(err, os.ErrNotExist) { 80 return false, nil 81 } 82 return false, err 83 } 84 85 func clientConfigPredefinedDirs() []string { 86 var predefinedDirs []string 87 exeFile, err := os.Executable() 88 if err != nil { 89 logger.Warnf("Unable to access the application directory for client configuration search, err: %v", err) 90 } else { 91 predefinedDirs = append(predefinedDirs, filepath.Dir(exeFile)) 92 } 93 homeDir, err := os.UserHomeDir() 94 if err != nil { 95 logger.Warnf("Unable to access Home directory for client configuration search, err: %v", err) 96 } else { 97 predefinedDirs = append(predefinedDirs, homeDir) 98 } 99 if predefinedDirs == nil { 100 return []string{} 101 } 102 return predefinedDirs 103 } 104 105 // ClientConfig config root 106 type ClientConfig struct { 107 Common *ClientConfigCommonProps `json:"common"` 108 } 109 110 // ClientConfigCommonProps properties from "common" section 111 type ClientConfigCommonProps struct { 112 LogLevel string `json:"log_level,omitempty"` 113 LogPath string `json:"log_path,omitempty"` 114 } 115 116 func parseClientConfiguration(filePath string) (*ClientConfig, error) { 117 if filePath == "" { 118 return nil, nil 119 } 120 fileContents, err := os.ReadFile(filePath) 121 if err != nil { 122 return nil, parsingClientConfigError(err) 123 } 124 err = validateCfgPerm(filePath) 125 if err != nil { 126 return nil, parsingClientConfigError(err) 127 } 128 var clientConfig ClientConfig 129 err = json.Unmarshal(fileContents, &clientConfig) 130 if err != nil { 131 return nil, parsingClientConfigError(err) 132 } 133 unknownValues := getUnknownValues(fileContents) 134 if len(unknownValues) > 0 { 135 for val := range unknownValues { 136 logger.Warnf("Unknown configuration entry: %s with value: %s", val, unknownValues[val]) 137 } 138 } 139 err = validateClientConfiguration(&clientConfig) 140 if err != nil { 141 return nil, parsingClientConfigError(err) 142 } 143 return &clientConfig, nil 144 } 145 146 func getUnknownValues(fileContents []byte) map[string]interface{} { 147 var values map[string]interface{} 148 err := json.Unmarshal(fileContents, &values) 149 if err != nil { 150 return nil 151 } 152 if values["common"] == nil { 153 return nil 154 } 155 commonValues := values["common"].(map[string]interface{}) 156 lowercaseCommonValues := make(map[string]interface{}, len(commonValues)) 157 for k, v := range commonValues { 158 lowercaseCommonValues[strings.ToLower(k)] = v 159 } 160 delete(lowercaseCommonValues, "log_level") 161 delete(lowercaseCommonValues, "log_path") 162 return lowercaseCommonValues 163 } 164 165 func parsingClientConfigError(err error) error { 166 return fmt.Errorf("parsing client config failed: %w", err) 167 } 168 169 func validateClientConfiguration(clientConfig *ClientConfig) error { 170 if clientConfig == nil { 171 return errors.New("client config not found") 172 } 173 if clientConfig.Common == nil { 174 return errors.New("common section in client config not found") 175 } 176 return validateLogLevel(*clientConfig) 177 } 178 179 func validateLogLevel(clientConfig ClientConfig) error { 180 var logLevel = clientConfig.Common.LogLevel 181 if logLevel != "" { 182 _, err := toLogLevel(logLevel) 183 if err != nil { 184 return err 185 } 186 } 187 return nil 188 } 189 190 func validateCfgPerm(filePath string) error { 191 if runtime.GOOS == "windows" { 192 return nil 193 } 194 stat, err := os.Stat(filePath) 195 if err != nil { 196 return err 197 } 198 perm := stat.Mode() 199 // Check if group (5th LSB) or others (2nd LSB) have a write permission to the file 200 if perm&(1<<4) != 0 || perm&(1<<1) != 0 { 201 return fmt.Errorf("configuration file: %s can be modified by group or others", filePath) 202 } 203 return nil 204 } 205 206 func toLogLevel(logLevelString string) (string, error) { 207 var logLevel = strings.ToUpper(logLevelString) 208 switch logLevel { 209 case levelOff, levelError, levelWarn, levelInfo, levelDebug, levelTrace: 210 return logLevel, nil 211 default: 212 return "", errors.New("unknown log level: " + logLevelString) 213 } 214 }