config_center/file/impl.go (236 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 file import ( "bytes" "errors" "os" "os/exec" "os/user" "path/filepath" "runtime" "strings" ) import ( gxset "github.com/dubbogo/gost/container/set" perrors "github.com/pkg/errors" ) import ( "dubbo.apache.org/dubbo-go/v3/common" "dubbo.apache.org/dubbo-go/v3/config_center" "dubbo.apache.org/dubbo-go/v3/config_center/parser" ) var osType = runtime.GOOS const ( windowsOS = "windows" ) const ( ParamNamePrefix = "dubbo.config-center." ConfigCenterDirParamName = ParamNamePrefix + "dir" ConfigCenterEncodingParamName = ParamNamePrefix + "encoding" defaultConfigCenterEncoding = "UTF-8" ) type FileSystemDynamicConfiguration struct { config_center.BaseDynamicConfiguration url *common.URL rootPath string encoding string cacheListener *CacheListener parser parser.ConfigurationParser } func newFileSystemDynamicConfiguration(url *common.URL) (*FileSystemDynamicConfiguration, error) { encode := url.GetParam(ConfigCenterEncodingParamName, defaultConfigCenterEncoding) root := url.GetParam(ConfigCenterDirParamName, "") var c *FileSystemDynamicConfiguration root, err := mkdirIfNecessary(root) if err != nil { return nil, err } c = &FileSystemDynamicConfiguration{ url: url, rootPath: root, encoding: encode, } c.cacheListener = NewCacheListener(c.rootPath) return c, nil } // RootPath get root path func (fsdc *FileSystemDynamicConfiguration) RootPath() string { return fsdc.rootPath } // Parser Get Parser func (fsdc *FileSystemDynamicConfiguration) Parser() parser.ConfigurationParser { return fsdc.parser } // SetParser Set Parser func (fsdc *FileSystemDynamicConfiguration) SetParser(p parser.ConfigurationParser) { fsdc.parser = p } // AddListener Add listener func (fsdc *FileSystemDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, opts ...config_center.Option) { tmpOpts := config_center.NewOptions(opts...) tmpPath := fsdc.GetPath(key, tmpOpts.Center.Group) fsdc.cacheListener.AddListener(tmpPath, listener) } // RemoveListener Remove listener func (fsdc *FileSystemDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener, opts ...config_center.Option) { tmpOpts := config_center.NewOptions(opts...) tmpPath := fsdc.GetPath(key, tmpOpts.Center.Group) fsdc.cacheListener.RemoveListener(tmpPath, listener) } // GetProperties get properties file func (fsdc *FileSystemDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) { tmpOpts := config_center.NewOptions(opts...) tmpPath := fsdc.GetPath(key, tmpOpts.Center.Group) file, err := os.ReadFile(tmpPath) if err != nil { return "", perrors.WithStack(err) } return string(file), nil } // GetRule get Router rule properties file func (fsdc *FileSystemDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) { return fsdc.GetProperties(key, opts...) } // GetInternalProperty get value by key in Default properties file(dubbo.properties) func (fsdc *FileSystemDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, error) { return fsdc.GetProperties(key, opts...) } // PublishConfig will publish the config with the (key, group, value) pair func (fsdc *FileSystemDynamicConfiguration) PublishConfig(key string, group string, value string) error { tmpPath := fsdc.GetPath(key, group) return fsdc.write2File(tmpPath, value) } // GetConfigKeysByGroup will return all keys with the group func (fsdc *FileSystemDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) { tmpPath := fsdc.GetPath("", group) r := gxset.NewSet() fileInfo, _ := os.ReadDir(tmpPath) for _, file := range fileInfo { // list file if file.IsDir() { continue } r.Add(file.Name()) } return r, nil } // RemoveConfig will remove tconfig_center/nacos/impl_testhe config whit hte (key, group) func (fsdc *FileSystemDynamicConfiguration) RemoveConfig(key string, group string) error { tmpPath := fsdc.GetPath(key, group) _, err := fsdc.deleteDelay(tmpPath) return err } // Close close file watcher func (fsdc *FileSystemDynamicConfiguration) Close() error { return fsdc.cacheListener.Close() } // GetPath get path func (fsdc *FileSystemDynamicConfiguration) GetPath(key string, group string) string { if len(key) == 0 { return filepath.Join(fsdc.rootPath, group) } if len(group) == 0 { group = config_center.DefaultGroup } return filepath.Join(fsdc.rootPath, group, adapterKey(key)) } func (fsdc *FileSystemDynamicConfiguration) deleteDelay(path string) (bool, error) { if len(path) == 0 { return false, nil } if err := os.RemoveAll(path); err != nil { return false, err } return true, nil } func (fsdc *FileSystemDynamicConfiguration) write2File(fp string, value string) error { if err := forceMkdirParent(fp); err != nil { return perrors.WithStack(err) } return os.WriteFile(fp, []byte(value), os.ModePerm) } func forceMkdirParent(fp string) error { return createDir(getParentDirectory(fp)) } func createDir(path string) error { // create dir, chmod is drwxrwxrwx(0777) if err := os.MkdirAll(path, os.ModePerm); err != nil { return err } return nil } func getParentDirectory(fp string) string { return substr(fp, 0, strings.LastIndex(fp, string(filepath.Separator))) } func substr(s string, pos, length int) string { runes := []rune(s) l := pos + length if l > len(runes) { l = len(runes) } return string(runes[pos:l]) } // Home returns the home directory for the executing user. // // This uses an OS-specific method for discovering the home directory. // An error is returned if a home directory cannot be detected. func Home() (string, error) { currentUser, err := user.Current() if nil == err { return currentUser.HomeDir, nil } // cross compile support if windowsOS == osType { return homeWindows() } // Unix-like system, so just assume Unix return homeUnix() } func homeUnix() (string, error) { // First prefer the HOME environmental variable if home := os.Getenv("HOME"); home != "" { return home, nil } // If that fails, try the shell var stdout bytes.Buffer cmd := exec.Command("sh", "-c", "eval echo ~$USER") cmd.Stdout = &stdout if err := cmd.Run(); err != nil { return "", err } result := strings.TrimSpace(stdout.String()) if len(result) == 0 { return "", errors.New("blank output when reading home directory") } return result, nil } func homeWindows() (string, error) { drive := os.Getenv("HOMEDRIVE") homePath := os.Getenv("HOMEPATH") home := drive + homePath if len(drive) == 0 || len(homePath) == 0 { home = os.Getenv("USERPROFILE") } if len(home) == 0 { return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") } return home, nil } func mkdirIfNecessary(urlRoot string) (string, error) { if !legalPath(urlRoot) { // not exist, use default, mac is: /XXX/xx/.dubbo/config-center rp, err := Home() if err != nil { return "", perrors.WithStack(err) } urlRoot = adapterUrl(rp) } if _, err := os.Stat(urlRoot); err != nil { // it must be dir, if not exist, will create if err = createDir(urlRoot); err != nil { return "", perrors.WithStack(err) } } return urlRoot, nil } func legalPath(path string) bool { if len(path) == 0 { return false } if _, err := os.Stat(path); err != nil { return false } return true } func adapterUrl(rp string) string { if osType == windowsOS { return filepath.Join(rp, "_dubbo", "config-center") } return filepath.Join(rp, ".dubbo", "config-center") } // used for GetPath. param key default is instance's id. // e.g: (ip:port) 127.0.0.1:20081, in windows env, will change to 127_0_0_1_20081 func adapterKey(key string) string { if len(key) == 0 { return "" } if osType == windowsOS { return strings.ReplaceAll(strings.ReplaceAll(key, ".", "_"), ":", "_") } return key }