dubbo.apache.org/dubbo-go/v3@v3.1.1/config_center/file/impl.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package file 19 20 import ( 21 "bytes" 22 "errors" 23 "os" 24 "os/exec" 25 "os/user" 26 "path/filepath" 27 "runtime" 28 "strings" 29 30 gxset "github.com/dubbogo/gost/container/set" 31 32 "dubbo.apache.org/dubbo-go/v3/common" 33 "dubbo.apache.org/dubbo-go/v3/config_center" 34 "dubbo.apache.org/dubbo-go/v3/config_center/parser" 35 perrors "github.com/pkg/errors" 36 ) 37 38 var osType = runtime.GOOS 39 40 const ( 41 windowsOS = "windows" 42 ) 43 44 const ( 45 ParamNamePrefix = "dubbo.config-center." 46 ConfigCenterDirParamName = ParamNamePrefix + "dir" 47 ConfigCenterEncodingParamName = ParamNamePrefix + "encoding" 48 defaultConfigCenterEncoding = "UTF-8" 49 ) 50 51 type FileSystemDynamicConfiguration struct { 52 config_center.BaseDynamicConfiguration 53 url *common.URL 54 rootPath string 55 encoding string 56 cacheListener *CacheListener 57 parser parser.ConfigurationParser 58 } 59 60 func newFileSystemDynamicConfiguration(url *common.URL) (*FileSystemDynamicConfiguration, error) { 61 encode := url.GetParam(ConfigCenterEncodingParamName, defaultConfigCenterEncoding) 62 63 root := url.GetParam(ConfigCenterDirParamName, "") 64 var c *FileSystemDynamicConfiguration 65 66 root, err := mkdirIfNecessary(root) 67 if err != nil { 68 return nil, err 69 } 70 71 c = &FileSystemDynamicConfiguration{ 72 url: url, 73 rootPath: root, 74 encoding: encode, 75 } 76 77 c.cacheListener = NewCacheListener(c.rootPath) 78 79 return c, nil 80 } 81 82 // RootPath get root path 83 func (fsdc *FileSystemDynamicConfiguration) RootPath() string { 84 return fsdc.rootPath 85 } 86 87 // Parser Get Parser 88 func (fsdc *FileSystemDynamicConfiguration) Parser() parser.ConfigurationParser { 89 return fsdc.parser 90 } 91 92 // SetParser Set Parser 93 func (fsdc *FileSystemDynamicConfiguration) SetParser(p parser.ConfigurationParser) { 94 fsdc.parser = p 95 } 96 97 // AddListener Add listener 98 func (fsdc *FileSystemDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, 99 opts ...config_center.Option) { 100 tmpOpts := &config_center.Options{} 101 for _, opt := range opts { 102 opt(tmpOpts) 103 } 104 105 tmpPath := fsdc.GetPath(key, tmpOpts.Group) 106 fsdc.cacheListener.AddListener(tmpPath, listener) 107 } 108 109 // RemoveListener Remove listener 110 func (fsdc *FileSystemDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener, 111 opts ...config_center.Option) { 112 tmpOpts := &config_center.Options{} 113 for _, opt := range opts { 114 opt(tmpOpts) 115 } 116 117 tmpPath := fsdc.GetPath(key, tmpOpts.Group) 118 fsdc.cacheListener.RemoveListener(tmpPath, listener) 119 } 120 121 // GetProperties get properties file 122 func (fsdc *FileSystemDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) { 123 tmpOpts := &config_center.Options{} 124 for _, opt := range opts { 125 opt(tmpOpts) 126 } 127 128 tmpPath := fsdc.GetPath(key, tmpOpts.Group) 129 file, err := os.ReadFile(tmpPath) 130 if err != nil { 131 return "", perrors.WithStack(err) 132 } 133 return string(file), nil 134 } 135 136 // GetRule get Router rule properties file 137 func (fsdc *FileSystemDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) { 138 return fsdc.GetProperties(key, opts...) 139 } 140 141 // GetInternalProperty get value by key in Default properties file(dubbo.properties) 142 func (fsdc *FileSystemDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, 143 error) { 144 return fsdc.GetProperties(key, opts...) 145 } 146 147 // PublishConfig will publish the config with the (key, group, value) pair 148 func (fsdc *FileSystemDynamicConfiguration) PublishConfig(key string, group string, value string) error { 149 tmpPath := fsdc.GetPath(key, group) 150 return fsdc.write2File(tmpPath, value) 151 } 152 153 // GetConfigKeysByGroup will return all keys with the group 154 func (fsdc *FileSystemDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) { 155 tmpPath := fsdc.GetPath("", group) 156 r := gxset.NewSet() 157 158 fileInfo, _ := os.ReadDir(tmpPath) 159 160 for _, file := range fileInfo { 161 // list file 162 if file.IsDir() { 163 continue 164 } 165 166 r.Add(file.Name()) 167 } 168 169 return r, nil 170 } 171 172 // RemoveConfig will remove tconfig_center/nacos/impl_testhe config whit hte (key, group) 173 func (fsdc *FileSystemDynamicConfiguration) RemoveConfig(key string, group string) error { 174 tmpPath := fsdc.GetPath(key, group) 175 _, err := fsdc.deleteDelay(tmpPath) 176 return err 177 } 178 179 // Close close file watcher 180 func (fsdc *FileSystemDynamicConfiguration) Close() error { 181 return fsdc.cacheListener.Close() 182 } 183 184 // GetPath get path 185 func (fsdc *FileSystemDynamicConfiguration) GetPath(key string, group string) string { 186 if len(key) == 0 { 187 return filepath.Join(fsdc.rootPath, group) 188 } 189 190 if len(group) == 0 { 191 group = config_center.DefaultGroup 192 } 193 194 return filepath.Join(fsdc.rootPath, group, adapterKey(key)) 195 } 196 197 func (fsdc *FileSystemDynamicConfiguration) deleteDelay(path string) (bool, error) { 198 if len(path) == 0 { 199 return false, nil 200 } 201 202 if err := os.RemoveAll(path); err != nil { 203 return false, err 204 } 205 206 return true, nil 207 } 208 209 func (fsdc *FileSystemDynamicConfiguration) write2File(fp string, value string) error { 210 if err := forceMkdirParent(fp); err != nil { 211 return perrors.WithStack(err) 212 } 213 214 return os.WriteFile(fp, []byte(value), os.ModePerm) 215 } 216 217 func forceMkdirParent(fp string) error { 218 return createDir(getParentDirectory(fp)) 219 } 220 221 func createDir(path string) error { 222 // create dir, chmod is drwxrwxrwx(0777) 223 if err := os.MkdirAll(path, os.ModePerm); err != nil { 224 return err 225 } 226 227 return nil 228 } 229 230 func getParentDirectory(fp string) string { 231 return substr(fp, 0, strings.LastIndex(fp, string(filepath.Separator))) 232 } 233 234 func substr(s string, pos, length int) string { 235 runes := []rune(s) 236 l := pos + length 237 if l > len(runes) { 238 l = len(runes) 239 } 240 241 return string(runes[pos:l]) 242 } 243 244 // Home returns the home directory for the executing user. 245 // 246 // This uses an OS-specific method for discovering the home directory. 247 // An error is returned if a home directory cannot be detected. 248 func Home() (string, error) { 249 currentUser, err := user.Current() 250 if nil == err { 251 return currentUser.HomeDir, nil 252 } 253 254 // cross compile support 255 if windowsOS == osType { 256 return homeWindows() 257 } 258 259 // Unix-like system, so just assume Unix 260 return homeUnix() 261 } 262 263 func homeUnix() (string, error) { 264 // First prefer the HOME environmental variable 265 if home := os.Getenv("HOME"); home != "" { 266 return home, nil 267 } 268 269 // If that fails, try the shell 270 var stdout bytes.Buffer 271 cmd := exec.Command("sh", "-c", "eval echo ~$USER") 272 cmd.Stdout = &stdout 273 if err := cmd.Run(); err != nil { 274 return "", err 275 } 276 277 result := strings.TrimSpace(stdout.String()) 278 if len(result) == 0 { 279 return "", errors.New("blank output when reading home directory") 280 } 281 282 return result, nil 283 } 284 285 func homeWindows() (string, error) { 286 drive := os.Getenv("HOMEDRIVE") 287 homePath := os.Getenv("HOMEPATH") 288 home := drive + homePath 289 if len(drive) == 0 || len(homePath) == 0 { 290 home = os.Getenv("USERPROFILE") 291 } 292 if len(home) == 0 { 293 return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") 294 } 295 296 return home, nil 297 } 298 299 func mkdirIfNecessary(urlRoot string) (string, error) { 300 if !legalPath(urlRoot) { 301 // not exist, use default, mac is: /XXX/xx/.dubbo/config-center 302 rp, err := Home() 303 if err != nil { 304 return "", perrors.WithStack(err) 305 } 306 307 urlRoot = adapterUrl(rp) 308 } 309 310 if _, err := os.Stat(urlRoot); err != nil { 311 // it must be dir, if not exist, will create 312 if err = createDir(urlRoot); err != nil { 313 return "", perrors.WithStack(err) 314 } 315 } 316 317 return urlRoot, nil 318 } 319 320 func legalPath(path string) bool { 321 if len(path) == 0 { 322 return false 323 } 324 if _, err := os.Stat(path); err != nil { 325 return false 326 } 327 328 return true 329 } 330 331 func adapterUrl(rp string) string { 332 if osType == windowsOS { 333 return filepath.Join(rp, "_dubbo", "config-center") 334 } 335 336 return filepath.Join(rp, ".dubbo", "config-center") 337 } 338 339 // used for GetPath. param key default is instance's id. 340 // e.g: (ip:port) 127.0.0.1:20081, in windows env, will change to 127_0_0_1_20081 341 func adapterKey(key string) string { 342 if len(key) == 0 { 343 return "" 344 } 345 346 if osType == windowsOS { 347 return strings.ReplaceAll(strings.ReplaceAll(key, ".", "_"), ":", "_") 348 } 349 350 return key 351 }