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  }