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 }