github.com/Microsoft/fabrikate@v0.0.0-20190420002442-bff75be28d02/core/componentConfig.go (about)

     1  package core
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path"
     9  	"strings"
    10  
    11  	"github.com/kyokomi/emoji"
    12  	log "github.com/sirupsen/logrus"
    13  	"github.com/timfpark/conjungo"
    14  	yaml "github.com/timfpark/yaml"
    15  )
    16  
    17  type ComponentConfig struct {
    18  	Path            string                     `yaml:"-" json:"-"`
    19  	Serialization   string                     `yaml:"-" json:"-"`
    20  	Namespace       string                     `yaml:"namespace,omitempty" json:"namespace,omitempty"`
    21  	InjectNamespace bool                       `yaml:"injectNamespace,omitempty" json:"injectNamespace,omitempty"`
    22  	Config          map[string]interface{}     `yaml:"config,omitempty" json:"config,omitempty"`
    23  	Subcomponents   map[string]ComponentConfig `yaml:"subcomponents,omitempty" json:"subcomponents,omitempty"`
    24  }
    25  
    26  func NewComponentConfig(path string) ComponentConfig {
    27  	return ComponentConfig{
    28  		Path:          path,
    29  		Config:        map[string]interface{}{},
    30  		Subcomponents: map[string]ComponentConfig{},
    31  	}
    32  }
    33  
    34  func (cc *ComponentConfig) GetPath(environment string) string {
    35  	configFilename := fmt.Sprintf("config/%s.%s", environment, cc.Serialization)
    36  	return path.Join(cc.Path, configFilename)
    37  }
    38  
    39  func (cc *ComponentConfig) UnmarshalJSONConfig(environment string) (err error) {
    40  	cc.Serialization = "json"
    41  	return UnmarshalFile(cc.GetPath(environment), json.Unmarshal, &cc)
    42  }
    43  
    44  func (cc *ComponentConfig) UnmarshalYAMLConfig(environment string) (err error) {
    45  	cc.Serialization = "yaml"
    46  	return UnmarshalFile(cc.GetPath(environment), yaml.Unmarshal, &cc)
    47  }
    48  
    49  func (cc *ComponentConfig) MergeConfigFile(path string, environment string) (err error) {
    50  	componentConfig := NewComponentConfig(path)
    51  	if err := componentConfig.Load(environment); err != nil {
    52  		return err
    53  	}
    54  
    55  	return cc.Merge(componentConfig)
    56  }
    57  
    58  func (cc *ComponentConfig) Load(environment string) (err error) {
    59  	err = cc.UnmarshalYAMLConfig(environment)
    60  
    61  	// fall back to looking for JSON if loading YAML fails.
    62  	if err != nil {
    63  		err = cc.UnmarshalJSONConfig(environment)
    64  
    65  		if err != nil {
    66  			// couldn't find any config files, so default back to yaml serialization
    67  			cc.Serialization = "yaml"
    68  		}
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  // HasComponentConfig checks if the component contains the given component configuration.
    75  // The given component is specified via a configuration `path`.
    76  // Returns true if it contains it, otherwise it returns false.
    77  func (cc *ComponentConfig) HasComponentConfig(path []string) (bool) {
    78  	configLevel := cc.Config
    79  	
    80  	for levelIndex, pathPart := range path {
    81  		// if this key is not the final one, we need to decend in the config.
    82  		if _, ok := configLevel[pathPart]; !ok {
    83  			return false;
    84  		}
    85  
    86  		if levelIndex < len(path)-1 {
    87  			configLevel = configLevel[pathPart].(map[string]interface{})
    88  		}
    89  	}
    90  
    91  	return true
    92  }
    93  
    94  // SetComponentConfig sets the `value` of the given configuration setting.
    95  // The configuration setting is indicated via a configuration `path`.
    96  func (cc *ComponentConfig) SetComponentConfig(path []string, value string) {
    97  	configLevel := cc.Config
    98  	createdNewConfig := false
    99  
   100  	for levelIndex, pathPart := range path {
   101  		// if this key is not the final one, we need to decend in the config.
   102  		if levelIndex < len(path)-1 {
   103  			if _, ok := configLevel[pathPart]; !ok {
   104  				createdNewConfig = true
   105  				configLevel[pathPart] = map[string]interface{}{}
   106  			}
   107  
   108  			configLevel = configLevel[pathPart].(map[string]interface{})
   109  		} else {
   110  			if createdNewConfig {
   111  				log.Info(emoji.Sprintf(":seedling: Created new value for %s", strings.Join(path, ".")))
   112  			}
   113  			configLevel[pathPart] = value
   114  		}
   115  	}
   116  }
   117  
   118  // GetSubcomponentConfig returns the subcomponent config of the given component.
   119  // If the subcomponent does not exist, it creates it
   120  //
   121  // Returns the subcomponent config
   122  func (cc *ComponentConfig) GetSubcomponentConfig(subcomponentPath []string) (subcomponentConfig ComponentConfig) {
   123  	subcomponentConfig = *cc
   124  	for _, subcomponentName := range subcomponentPath {
   125  		if subcomponentConfig.Subcomponents == nil {
   126  			subcomponentConfig.Subcomponents = map[string]ComponentConfig{}
   127  		}
   128  
   129  		if _, ok := subcomponentConfig.Subcomponents[subcomponentName]; !ok {
   130  			log.Info(emoji.Sprintf(":seedling: Creating new subcomponent configuration for %s", subcomponentName))
   131  			subcomponentConfig.Subcomponents[subcomponentName] = NewComponentConfig(".")
   132  		}
   133  
   134  		subcomponentConfig = subcomponentConfig.Subcomponents[subcomponentName]
   135  	}
   136  
   137  	return subcomponentConfig
   138  }
   139  
   140  
   141  // HasSubcomponentConfig checks if a component contains the given subcomponents of the `subcomponentPath`
   142  //
   143  // Returns true if it contains the subcomponent, otherwise it returns false
   144  func (cc *ComponentConfig) HasSubcomponentConfig(subcomponentPath []string) (bool) {
   145  	subcomponentConfig := *cc 
   146  
   147  	for _, subcomponentName := range subcomponentPath {
   148  		if subcomponentConfig.Subcomponents == nil {
   149  			return false;
   150  		}
   151  
   152  		if _, ok := subcomponentConfig.Subcomponents[subcomponentName]; !ok {
   153  			return false;
   154  		}
   155  
   156  		subcomponentConfig = subcomponentConfig.Subcomponents[subcomponentName]
   157  	}
   158  
   159  	return true;
   160  }
   161  
   162  // SetConfig sets or creates the configuration `value` for the given `subcomponentPath`.
   163  func (cc *ComponentConfig) SetConfig(subcomponentPath []string, path []string, value string) {
   164  	subcomponentConfig := cc.GetSubcomponentConfig(subcomponentPath)
   165  	subcomponentConfig.SetComponentConfig(path, value)
   166  }
   167  
   168  func (cc *ComponentConfig) MergeNamespaces(newConfig ComponentConfig) ComponentConfig {
   169  	if cc.Namespace == "" {
   170  		cc.Namespace = newConfig.Namespace
   171  		cc.InjectNamespace = newConfig.InjectNamespace
   172  	}
   173  
   174  	for key, config := range cc.Subcomponents {
   175  		cc.Subcomponents[key] = config.MergeNamespaces(newConfig.Subcomponents[key])
   176  	}
   177  
   178  	return *cc
   179  }
   180  
   181  func (cc *ComponentConfig) Merge(newConfig ComponentConfig) (err error) {
   182  	options := conjungo.NewOptions()
   183  	options.Overwrite = false
   184  
   185  	err = conjungo.Merge(cc, newConfig, options)
   186  
   187  	cc.MergeNamespaces(newConfig)
   188  
   189  	return err
   190  }
   191  
   192  func (cc *ComponentConfig) Write(environment string) (err error) {
   193  	var marshaledConfig []byte
   194  
   195  	_ = os.Mkdir(cc.Path, os.ModePerm)
   196  	_ = os.Mkdir(path.Join(cc.Path, "config"), os.ModePerm)
   197  
   198  	if cc.Serialization == "json" {
   199  		marshaledConfig, err = json.MarshalIndent(cc, "", "  ")
   200  	} else {
   201  		marshaledConfig, err = yaml.Marshal(cc)
   202  	}
   203  
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	return ioutil.WriteFile(cc.GetPath(environment), marshaledConfig, 0644)
   209  }