blob: 9d8254026bf3740631798bb0906436029be15abd [file] [log] [blame]
/*
* 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"
"io/ioutil"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"runtime"
"strings"
)
import (
gxset "github.com/dubbogo/gost/container/set"
perrors "github.com/pkg/errors"
)
import (
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/config_center"
"github.com/apache/dubbo-go/config_center/parser"
)
const (
PARAM_NAME_PREFIX = "dubbo.config-center."
CONFIG_CENTER_DIR_PARAM_NAME = PARAM_NAME_PREFIX + "dir"
CONFIG_CENTER_ENCODING_PARAM_NAME = PARAM_NAME_PREFIX + "encoding"
DEFAULT_CONFIG_CENTER_ENCODING = "UTF-8"
)
// FileSystemDynamicConfiguration
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(CONFIG_CENTER_ENCODING_PARAM_NAME, DEFAULT_CONFIG_CENTER_ENCODING)
root := url.GetParam(CONFIG_CENTER_DIR_PARAM_NAME, "")
var c *FileSystemDynamicConfiguration
if _, err := os.Stat(root); err != nil {
// not exist, use default, /XXX/xx/.dubbo/config-center
if rp, err := Home(); err != nil {
return nil, perrors.WithStack(err)
} else {
root = path.Join(rp, ".dubbo", "config-center")
}
}
if _, err := os.Stat(root); err != nil {
// it must be dir, if not exist, will create
if err = createDir(root); err != nil {
return nil, perrors.WithStack(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.Options{}
for _, opt := range opts {
opt(tmpOpts)
}
path := fsdc.GetPath(key, tmpOpts.Group)
fsdc.cacheListener.AddListener(path, listener)
}
// RemoveListener Remove listener
func (fsdc *FileSystemDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener,
opts ...config_center.Option) {
tmpOpts := &config_center.Options{}
for _, opt := range opts {
opt(tmpOpts)
}
path := fsdc.GetPath(key, tmpOpts.Group)
fsdc.cacheListener.RemoveListener(path, listener)
}
// GetProperties get properties file
func (fsdc *FileSystemDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) {
tmpOpts := &config_center.Options{}
for _, opt := range opts {
opt(tmpOpts)
}
path := fsdc.GetPath(key, tmpOpts.Group)
file, err := ioutil.ReadFile(path)
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 {
path := fsdc.GetPath(key, group)
return fsdc.write2File(path, value)
}
// GetConfigKeysByGroup will return all keys with the group
func (fsdc *FileSystemDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
path := fsdc.GetPath("", group)
r := gxset.NewSet()
fileInfo, _ := ioutil.ReadDir(path)
for _, file := range fileInfo {
// list file
if file.IsDir() {
continue
}
r.Add(file.Name())
}
return r, nil
}
// RemoveConfig will remove the config whit hte (key, group)
func (fsdc *FileSystemDynamicConfiguration) RemoveConfig(key string, group string) error {
path := fsdc.GetPath(key, group)
_, err := fsdc.deleteDelay(path)
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 path.Join(fsdc.rootPath, group)
}
if len(group) == 0 {
group = config_center.DEFAULT_GROUP
}
return path.Join(fsdc.rootPath, group, key)
}
func (fsdc *FileSystemDynamicConfiguration) deleteDelay(path string) (bool, error) {
if path == "" {
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 ioutil.WriteFile(fp, []byte(value), os.ModePerm)
}
func forceMkdirParent(fp string) error {
pd := getParentDirectory(fp)
return createDir(pd)
}
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) {
user, err := user.Current()
if nil == err {
return user.HomeDir, nil
}
// cross compile support
if "windows" == runtime.GOOS {
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 result == "" {
return "", errors.New("blank output when reading home directory")
}
return result, nil
}
func homeWindows() (string, error) {
drive := os.Getenv("HOMEDRIVE")
path := os.Getenv("HOMEPATH")
home := drive + path
if drive == "" || path == "" {
home = os.Getenv("USERPROFILE")
}
if home == "" {
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
}
return home, nil
}