config_center/parser/configuration_parser.go (243 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 parser import ( "strconv" "strings" ) import ( "github.com/dubbogo/gost/log/logger" "github.com/magiconair/properties" perrors "github.com/pkg/errors" "gopkg.in/yaml.v2" ) import ( "dubbo.apache.org/dubbo-go/v3/common" "dubbo.apache.org/dubbo-go/v3/common/constant" ) const ( ScopeApplication = "application" GeneralType = "general" ) // ConfigurationParser interface type ConfigurationParser interface { Parse(string) (map[string]string, error) ParseToUrls(content string) ([]*common.URL, error) } // DefaultConfigurationParser for supporting properties file in config center type DefaultConfigurationParser struct{} // ConfiguratorConfig defines configurator config type ConfiguratorConfig struct { ConfigVersion string `yaml:"configVersion"` Scope string `yaml:"scope"` Key string `yaml:"key"` Enabled bool `yaml:"enabled"` Configs []ConfigItem `yaml:"configs"` } // ConfigItem defines config item type ConfigItem struct { Type string `yaml:"type"` Enabled bool `yaml:"enabled"` Addresses []string `yaml:"addresses"` ProviderAddresses []string `yaml:"providerAddresses"` Services []string `yaml:"services"` Applications []string `yaml:"applications"` Parameters map[string]string `yaml:"parameters"` Side string `yaml:"side"` Match *ConditionMatch `yaml:"match"` } type ConditionMatch struct { Address *common.AddressMatch `yaml:"address"` ProviderAddress *common.AddressMatch `yaml:"providerAddress"` Service *common.ListStringMatch `yaml:"service"` App *common.ListStringMatch `yaml:"app"` Param []*common.ParamMatch `yaml:"param"` } func (c *ConditionMatch) IsMatch(host string, url *common.URL) bool { if !c.Address.IsMatch(host) { return false } if !c.ProviderAddress.IsMatch(url.Location) { return false } if !c.Service.IsMatch(url.ServiceKey()) { return false } if !c.App.IsMatch(url.GetParam(constant.ApplicationKey, "")) { return false } if c.Param != nil { for _, p := range c.Param { if !p.IsMatch(url) { return false } } } return true } // Parse load content func (parser *DefaultConfigurationParser) Parse(content string) (map[string]string, error) { pps, err := properties.LoadString(content) if err != nil { logger.Errorf("Parse the content {%v} in DefaultConfigurationParser error ,error message is {%v}", content, err) return nil, err } return pps.Map(), nil } // ParseToUrls is used to parse content to urls func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common.URL, error) { config := ConfiguratorConfig{} if err := yaml.Unmarshal([]byte(content), &config); err != nil { return nil, err } scope := config.Scope items := config.Configs var allUrls []*common.URL if scope == ScopeApplication { for _, v := range items { urls, err := appItemToUrls(v, config) if err != nil { return nil, err } allUrls = append(allUrls, urls...) } } else { for _, v := range items { urls, err := serviceItemToUrls(v, config) if err != nil { return nil, err } allUrls = append(allUrls, urls...) } } return allUrls, nil } // serviceItemToUrls is used to transfer item and config to urls func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { addresses := item.Addresses if len(addresses) == 0 { addresses = append(addresses, constant.AnyHostValue) } var urls []*common.URL for _, v := range addresses { urlStr := constant.OverrideProtocol + "://" + v + "/" serviceStr, err := getServiceString(config.Key) if err != nil { return nil, perrors.WithStack(err) } urlStr = urlStr + serviceStr paramStr, err := getParamString(item) if err != nil { return nil, perrors.WithStack(err) } urlStr = urlStr + paramStr urlStr = urlStr + getEnabledString(item, config) urlStr = urlStr + "&category=" urlStr = urlStr + constant.DynamicConfiguratorsCategory urlStr = urlStr + "&configVersion=" urlStr = urlStr + config.ConfigVersion apps := item.Applications if len(apps) > 0 { for _, v := range apps { newUrlStr := urlStr newUrlStr = newUrlStr + "&application" newUrlStr = newUrlStr + v url, err := common.NewURL(newUrlStr) if err != nil { return nil, perrors.WithStack(err) } url.SetAttribute(constant.MatchCondition, item.Match) urls = append(urls, url) } } else { url, err := common.NewURL(urlStr) if err != nil { return nil, perrors.WithStack(err) } url.SetAttribute(constant.MatchCondition, item.Match) urls = append(urls, url) } } return urls, nil } // nolint func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { addresses := item.Addresses if len(addresses) == 0 { addresses = append(addresses, constant.AnyHostValue) } var urls []*common.URL for _, v := range addresses { urlStr := constant.OverrideProtocol + "://" + v + "/" services := item.Services if len(services) == 0 { services = append(services, constant.AnyValue) } for _, vs := range services { serviceStr, err := getServiceString(vs) if err != nil { return nil, perrors.WithStack(err) } urlStr = urlStr + serviceStr paramStr, err := getParamString(item) if err != nil { return nil, perrors.WithStack(err) } urlStr = urlStr + paramStr urlStr = urlStr + "&application=" urlStr = urlStr + config.Key urlStr = urlStr + getEnabledString(item, config) urlStr = urlStr + "&category=" urlStr = urlStr + constant.AppDynamicConfiguratorsCategory urlStr = urlStr + "&configVersion=" urlStr = urlStr + config.ConfigVersion url, err := common.NewURL(urlStr) if err != nil { return nil, perrors.WithStack(err) } url.SetAttribute(constant.MatchCondition, item.Match) urls = append(urls, url) } } return urls, nil } // getServiceString returns service string func getServiceString(service string) (string, error) { if len(service) == 0 { return "", perrors.New("service field in configuration is null.") } var serviceStr string i := strings.Index(service, "/") if i > 0 { serviceStr = serviceStr + "group=" serviceStr = serviceStr + service[0:i] serviceStr = serviceStr + "&" service = service[i+1:] } j := strings.Index(service, ":") if j > 0 { serviceStr = serviceStr + "version=" serviceStr = serviceStr + service[j+1:] serviceStr = serviceStr + "&" service = service[0:j] } serviceStr = service + "?" + serviceStr return serviceStr, nil } // nolint func getParamString(item ConfigItem) (string, error) { var retStr string retStr = retStr + "category=" retStr = retStr + constant.DynamicConfiguratorsCategory if len(item.Side) > 0 { retStr = retStr + "&side=" retStr = retStr + item.Side } params := item.Parameters if len(params) <= 0 { return "", perrors.New("Invalid configurator rule, please specify at least one parameter " + "you want to change in the rule.") } for k, v := range params { retStr += "&" + k + "=" + v } retStr += "&" + constant.OverrideProvidersKey + "=" + strings.Join(item.ProviderAddresses, ",") return retStr, nil } // getEnabledString returns enabled string func getEnabledString(item ConfigItem, config ConfiguratorConfig) string { retStr := "&enabled=" if len(item.Type) == 0 || item.Type == GeneralType { retStr = retStr + strconv.FormatBool(config.Enabled) } else { retStr = retStr + strconv.FormatBool(item.Enabled) } return retStr }