package config // Merge merges two configurations into a single configuration. // // Merge allows for the two configurations to have duplicate resources, // because the resources will be merged. This differs from a single // Config which must only have unique resources. func Merge(c1, c2 *Config) (*Config, error) { c := new(Config) // Merge unknown keys unknowns := make(map[string]struct{}) for _, k := range c1.unknownKeys { _, present := unknowns[k] if !present { unknowns[k] = struct{}{} c.unknownKeys = append(c.unknownKeys, k) } } for _, k := range c2.unknownKeys { _, present := unknowns[k] if !present { unknowns[k] = struct{}{} c.unknownKeys = append(c.unknownKeys, k) } } // Merge Atlas configuration. This is a dumb one overrides the other // sort of merge. c.Atlas = c1.Atlas if c2.Atlas != nil { c.Atlas = c2.Atlas } // Merge the Terraform configuration if c1.Terraform != nil { c.Terraform = c1.Terraform if c2.Terraform != nil { c.Terraform.Merge(c2.Terraform) } } else { c.Terraform = c2.Terraform } // NOTE: Everything below is pretty gross. Due to the lack of generics // in Go, there is some hoop-jumping involved to make this merging a // little more test-friendly and less repetitive. Ironically, making it // less repetitive involves being a little repetitive, but I prefer to // be repetitive with things that are less error prone than things that // are more error prone (more logic). Type conversions to an interface // are pretty low-error. var m1, m2, mresult []merger // Modules m1 = make([]merger, 0, len(c1.Modules)) m2 = make([]merger, 0, len(c2.Modules)) for _, v := range c1.Modules { m1 = append(m1, v) } for _, v := range c2.Modules { m2 = append(m2, v) } mresult = mergeSlice(m1, m2) if len(mresult) > 0 { c.Modules = make([]*Module, len(mresult)) for i, v := range mresult { c.Modules[i] = v.(*Module) } } // Outputs m1 = make([]merger, 0, len(c1.Outputs)) m2 = make([]merger, 0, len(c2.Outputs)) for _, v := range c1.Outputs { m1 = append(m1, v) } for _, v := range c2.Outputs { m2 = append(m2, v) } mresult = mergeSlice(m1, m2) if len(mresult) > 0 { c.Outputs = make([]*Output, len(mresult)) for i, v := range mresult { c.Outputs[i] = v.(*Output) } } // Provider Configs m1 = make([]merger, 0, len(c1.ProviderConfigs)) m2 = make([]merger, 0, len(c2.ProviderConfigs)) for _, v := range c1.ProviderConfigs { m1 = append(m1, v) } for _, v := range c2.ProviderConfigs { m2 = append(m2, v) } mresult = mergeSlice(m1, m2) if len(mresult) > 0 { c.ProviderConfigs = make([]*ProviderConfig, len(mresult)) for i, v := range mresult { c.ProviderConfigs[i] = v.(*ProviderConfig) } } // Resources m1 = make([]merger, 0, len(c1.Resources)) m2 = make([]merger, 0, len(c2.Resources)) for _, v := range c1.Resources { m1 = append(m1, v) } for _, v := range c2.Resources { m2 = append(m2, v) } mresult = mergeSlice(m1, m2) if len(mresult) > 0 { c.Resources = make([]*Resource, len(mresult)) for i, v := range mresult { c.Resources[i] = v.(*Resource) } } // Variables m1 = make([]merger, 0, len(c1.Variables)) m2 = make([]merger, 0, len(c2.Variables)) for _, v := range c1.Variables { m1 = append(m1, v) } for _, v := range c2.Variables { m2 = append(m2, v) } mresult = mergeSlice(m1, m2) if len(mresult) > 0 { c.Variables = make([]*Variable, len(mresult)) for i, v := range mresult { c.Variables[i] = v.(*Variable) } } // Local Values // These are simpler than the other config elements because they are just // flat values and so no deep merging is required. if localsCount := len(c1.Locals) + len(c2.Locals); localsCount != 0 { // Explicit length check above because we want c.Locals to remain // nil if the result would be empty. c.Locals = make([]*Local, 0, len(c1.Locals)+len(c2.Locals)) c.Locals = append(c.Locals, c1.Locals...) c.Locals = append(c.Locals, c2.Locals...) } return c, nil } // merger is an interface that must be implemented by types that are // merge-able. This simplifies the implementation of Merge for the various // components of a Config. type merger interface { mergerName() string mergerMerge(merger) merger } // mergeSlice merges a slice of mergers. func mergeSlice(m1, m2 []merger) []merger { r := make([]merger, len(m1), len(m1)+len(m2)) copy(r, m1) m := map[string]struct{}{} for _, v2 := range m2 { // If we already saw it, just append it because its a // duplicate and invalid... name := v2.mergerName() if _, ok := m[name]; ok { r = append(r, v2) continue } m[name] = struct{}{} // Find an original to override var original merger originalIndex := -1 for i, v := range m1 { if v.mergerName() == name { originalIndex = i original = v break } } var v merger if original == nil { v = v2 } else { v = original.mergerMerge(v2) } if originalIndex == -1 { r = append(r, v) } else { r[originalIndex] = v } } return r }