vitess.io/vitess@v0.16.2/go/vt/vtadmin/cluster/file_config.go (about)

     1  /*
     2  Copyright 2020 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cluster
    18  
    19  import (
    20  	"strings"
    21  
    22  	"github.com/spf13/viper"
    23  )
    24  
    25  // FileConfig represents the structure of a set of cluster configs on disk. It
    26  // contains both a default config, and cluster-specific overrides. Viper is used
    27  // internally to load the config file, so any file format supported by viper is
    28  // permitted.
    29  //
    30  // A valid YAML config looks like:
    31  //
    32  //	defaults:
    33  //		discovery: k8s
    34  //	clusters:
    35  //		clusterID1:
    36  //			name: clusterName1
    37  //			discovery-k8s-some-flag: some-val
    38  //		clusterID2:
    39  //			name: clusterName2
    40  //			discovery: consul
    41  type FileConfig struct {
    42  	Defaults Config
    43  	Clusters map[string]Config
    44  }
    45  
    46  // String is part of the flag.Value interface.
    47  func (fc *FileConfig) String() string {
    48  	buf := strings.Builder{}
    49  
    50  	// inlining this produces "String is not in the method set of ClustersFlag"
    51  	cf := ClustersFlag(fc.Clusters)
    52  
    53  	buf.WriteString("{defaults: ")
    54  	buf.WriteString(fc.Defaults.String())
    55  	buf.WriteString(", clusters: ")
    56  	buf.WriteString(cf.String())
    57  	buf.WriteString("}")
    58  
    59  	return buf.String()
    60  }
    61  
    62  // Type is part of the pflag.Value interface.
    63  func (fc *FileConfig) Type() string {
    64  	return "cluster.FileConfig"
    65  }
    66  
    67  // Set is part of the flag.Value interface. It loads the file configuration
    68  // found at the path passed to the flag. Any config file format supported by
    69  // viper is supported by FileConfig.
    70  func (fc *FileConfig) Set(value string) error {
    71  	v := viper.New()
    72  	v.SetConfigFile(value)
    73  	if err := v.ReadInConfig(); err != nil {
    74  		return err
    75  	}
    76  
    77  	return fc.unmarshalViper(v)
    78  }
    79  
    80  func (fc *FileConfig) unmarshalViper(v *viper.Viper) error {
    81  	tmp := struct { // work around mapstructure's interface; see https://github.com/spf13/viper/issues/338#issuecomment-382376136
    82  		Defaults map[string]string
    83  		Clusters map[string]map[string]string
    84  	}{
    85  		Defaults: map[string]string{},
    86  		Clusters: map[string]map[string]string{},
    87  	}
    88  
    89  	if err := v.Unmarshal(&tmp); err != nil {
    90  		return err
    91  	}
    92  
    93  	if err := fc.Defaults.unmarshalMap(tmp.Defaults); err != nil {
    94  		return err
    95  	}
    96  
    97  	if fc.Clusters == nil {
    98  		fc.Clusters = map[string]Config{}
    99  	}
   100  	for id, clusterMap := range tmp.Clusters {
   101  		c, ok := fc.Clusters[id]
   102  		if !ok {
   103  			c = Config{
   104  				ID:                   id,
   105  				DiscoveryFlagsByImpl: map[string]map[string]string{},
   106  				VtSQLFlags:           map[string]string{},
   107  				VtctldFlags:          map[string]string{},
   108  			}
   109  		}
   110  
   111  		if err := c.unmarshalMap(clusterMap); err != nil {
   112  			return err
   113  		}
   114  
   115  		fc.Clusters[id] = c
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Combine combines a FileConfig with a default Config and a ClustersFlag (each
   122  // defined on the command-line) into a slice of final Configs that are suitable
   123  // to use for cluster creation.
   124  //
   125  // Combination uses the following precedence:
   126  // 1. Command-line cluster-specific overrides.
   127  // 2. File-based cluster-specific overrides.
   128  // 3. Command-line cluster defaults.
   129  // 4. File-based cluster defaults.
   130  func (fc *FileConfig) Combine(defaults Config, clusters map[string]Config) []Config {
   131  	configs := make([]Config, 0, len(clusters))
   132  	merged := map[string]bool{}
   133  
   134  	combinedDefaults := fc.Defaults.Merge(defaults)
   135  
   136  	for name, cfg := range fc.Clusters {
   137  		merged[name] = true
   138  
   139  		override, ok := clusters[name]
   140  		if !ok {
   141  			configs = append(configs, combinedDefaults.Merge(cfg))
   142  			continue
   143  		}
   144  
   145  		combinedOverrides := cfg.Merge(override)
   146  		configs = append(configs, combinedDefaults.Merge(combinedOverrides))
   147  	}
   148  
   149  	for name, cfg := range clusters {
   150  		if _, ok := merged[name]; ok {
   151  			continue
   152  		}
   153  
   154  		configs = append(configs, combinedDefaults.Merge(cfg))
   155  	}
   156  
   157  	return configs
   158  }