// Copyright (c) 2022 Alibaba Group Holding Ltd. // // Licensed 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 config import ( "encoding/json" "fmt" "regexp" "strings" . "github.com/alibaba/higress/v2/pkg/ingress/log" "google.golang.org/protobuf/proto" "istio.io/istio/pkg/config" ) // TemplateProcessor handles template substitution in configs type TemplateProcessor struct { // getValue is a function that retrieves values by type, namespace, name and key getValue func(valueType, namespace, name, key string) (string, error) namespace string secretConfigMgr *SecretConfigMgr } // NewTemplateProcessor creates a new TemplateProcessor with the given value getter function func NewTemplateProcessor(getValue func(valueType, namespace, name, key string) (string, error), namespace string, secretConfigMgr *SecretConfigMgr) *TemplateProcessor { return &TemplateProcessor{ getValue: getValue, namespace: namespace, secretConfigMgr: secretConfigMgr, } } // ProcessConfig processes a config and substitutes any template variables func (p *TemplateProcessor) ProcessConfig(cfg *config.Config) error { // Convert spec to JSON string to process substitutions jsonBytes, err := json.Marshal(cfg.Spec) if err != nil { return fmt.Errorf("failed to marshal config spec: %v", err) } configStr := string(jsonBytes) // Find all value references in format: // ${type.name.key} or ${type.namespace/name.key} valueRegex := regexp.MustCompile(`\$\{([^.}]+)\.(?:([^/]+)/)?([^.}]+)\.([^}]+)\}`) matches := valueRegex.FindAllStringSubmatch(configStr, -1) // If there are no value references, return immediately if len(matches) == 0 { if p.secretConfigMgr != nil { if err := p.secretConfigMgr.DeleteConfig(cfg); err != nil { IngressLog.Errorf("failed to delete secret dependency: %v", err) } } return nil } foundSecretSource := false IngressLog.Infof("start to apply config %s/%s with %d variables", cfg.Namespace, cfg.Name, len(matches)) for _, match := range matches { valueType := match[1] var namespace, name, key string if match[2] != "" { // Format: ${type.namespace/name.key} namespace = match[2] } else { // Format: ${type.name.key} - use default namespace namespace = p.namespace } name = match[3] key = match[4] // Get value using the provided getter function value, err := p.getValue(valueType, namespace, name, key) if err != nil { return fmt.Errorf("failed to get %s value for %s/%s.%s: %v", valueType, namespace, name, key, err) } // Add secret dependency if this is a secret reference if valueType == "secret" && p.secretConfigMgr != nil { foundSecretSource = true secretKey := fmt.Sprintf("%s/%s", namespace, name) if err := p.secretConfigMgr.AddConfig(secretKey, cfg); err != nil { IngressLog.Errorf("failed to add secret dependency: %v", err) } } // Replace placeholder with actual value configStr = strings.Replace(configStr, match[0], value, 1) } // Create a new instance of the same type as cfg.Spec newSpec := proto.Clone(cfg.Spec.(proto.Message)) if err := json.Unmarshal([]byte(configStr), newSpec); err != nil { return fmt.Errorf("failed to unmarshal substituted config: %v", err) } cfg.Spec = newSpec // Delete secret dependency if no secret reference is found if !foundSecretSource { if p.secretConfigMgr != nil { if err := p.secretConfigMgr.DeleteConfig(cfg); err != nil { IngressLog.Errorf("failed to delete secret dependency: %v", err) } } } IngressLog.Infof("end to process config %s/%s", cfg.Namespace, cfg.Name) return nil }