// LoadProvidersHcl recurses into the given HCL object and turns // it into a mapping of provider configs. func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { var objects []*hclobj.Object // Iterate over all the "provider" blocks and get the keys along with // their raw configuration objects. We'll parse those later. for _, o1 := range os.Elem(false) { for _, o2 := range o1.Elem(true) { objects = append(objects, o2) } } if len(objects) == 0 { return nil, nil } // Go through each object and turn it into an actual result. result := make([]*ProviderConfig, 0, len(objects)) for _, o := range objects { var config map[string]interface{} if err := hcl.DecodeObject(&config, o); err != nil { return nil, err } delete(config, "alias") rawConfig, err := NewRawConfig(config) if err != nil { return nil, fmt.Errorf( "Error reading config for provider config %s: %s", o.Key, err) } // If we have an alias field, then add those in var alias string if a := o.Get("alias", false); a != nil { err := hcl.DecodeObject(&alias, a) if err != nil { return nil, fmt.Errorf( "Error reading alias for provider[%s]: %s", o.Key, err) } } result = append(result, &ProviderConfig{ Name: o.Key, Alias: alias, RawConfig: rawConfig, }) } return result, nil }
func (d *decoder) decodeSlice(name string, o *hcl.Object, result reflect.Value) error { // If we have an interface, then we can address the interface, // but not the slice itself, so get the element but set the interface set := result if result.Kind() == reflect.Interface { result = result.Elem() } // Create the slice if it isn't nil resultType := result.Type() resultElemType := resultType.Elem() if result.IsNil() { resultSliceType := reflect.SliceOf(resultElemType) result = reflect.MakeSlice( resultSliceType, 0, 0) } // Determine how we're doing this expand := true switch o.Type { case hcl.ValueTypeObject: expand = false default: // Array or anything else: we expand values and take it all } i := 0 for _, o := range o.Elem(expand) { fieldName := fmt.Sprintf("%s[%d]", name, i) // Decode val := reflect.Indirect(reflect.New(resultElemType)) if err := d.decode(fieldName, o, val); err != nil { return err } // Append it onto the slice result = reflect.Append(result, val) i += 1 } set.Set(result) return nil }
// LoadOutputsHcl recurses into the given HCL object and turns // it into a mapping of outputs. func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) { objects := make(map[string]*hclobj.Object) // Iterate over all the "output" blocks and get the keys along with // their raw configuration objects. We'll parse those later. for _, o1 := range os.Elem(false) { for _, o2 := range o1.Elem(true) { objects[o2.Key] = o2 } } if len(objects) == 0 { return nil, nil } // Go through each object and turn it into an actual result. result := make([]*Output, 0, len(objects)) for n, o := range objects { var config map[string]interface{} if err := hcl.DecodeObject(&config, o); err != nil { return nil, err } rawConfig, err := NewRawConfig(config) if err != nil { return nil, fmt.Errorf( "Error reading config for output %s: %s", n, err) } result = append(result, &Output{ Name: n, RawConfig: rawConfig, }) } return result, nil }
func (d *decoder) decodeStruct(name string, o *hcl.Object, result reflect.Value) error { if o.Type != hcl.ValueTypeObject { return fmt.Errorf("%s: not an object type for struct (%v)", name, o.Type) } // This slice will keep track of all the structs we'll be decoding. // There can be more than one struct if there are embedded structs // that are squashed. structs := make([]reflect.Value, 1, 5) structs[0] = result // Compile the list of all the fields that we're going to be decoding // from all the structs. fields := make(map[*reflect.StructField]reflect.Value) for len(structs) > 0 { structVal := structs[0] structs = structs[1:] structType := structVal.Type() for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) if fieldType.Anonymous { fieldKind := fieldType.Type.Kind() if fieldKind != reflect.Struct { return fmt.Errorf( "%s: unsupported type to struct: %s", fieldType.Name, fieldKind) } // We have an embedded field. We "squash" the fields down // if specified in the tag. squash := false tagParts := strings.Split(fieldType.Tag.Get(tagName), ",") for _, tag := range tagParts[1:] { if tag == "squash" { squash = true break } } if squash { structs = append( structs, result.FieldByName(fieldType.Name)) continue } } // Normal struct field, store it away fields[&fieldType] = structVal.Field(i) } } usedKeys := make(map[string]struct{}) decodedFields := make([]string, 0, len(fields)) decodedFieldsVal := make([]reflect.Value, 0) unusedKeysVal := make([]reflect.Value, 0) for fieldType, field := range fields { if !field.IsValid() { // This should never happen panic("field is not valid") } // If we can't set the field, then it is unexported or something, // and we just continue onwards. if !field.CanSet() { continue } fieldName := fieldType.Name // This is whether or not we expand the object into its children // later. expand := false tagValue := fieldType.Tag.Get(tagName) tagParts := strings.SplitN(tagValue, ",", 2) if len(tagParts) >= 2 { switch tagParts[1] { case "expand": expand = true case "decodedFields": decodedFieldsVal = append(decodedFieldsVal, field) continue case "key": field.SetString(o.Key) continue case "unusedKeys": unusedKeysVal = append(unusedKeysVal, field) continue } } if tagParts[0] != "" { fieldName = tagParts[0] } // Find the element matching this name obj := o.Get(fieldName, true) if obj == nil { continue } // Track the used key usedKeys[fieldName] = struct{}{} // Create the field name and decode. We range over the elements // because we actually want the value. fieldName = fmt.Sprintf("%s.%s", name, fieldName) for _, obj := range obj.Elem(expand) { if err := d.decode(fieldName, obj, field); err != nil { return err } } decodedFields = append(decodedFields, fieldType.Name) } if len(decodedFieldsVal) > 0 { // Sort it so that it is deterministic sort.Strings(decodedFields) for _, v := range decodedFieldsVal { v.Set(reflect.ValueOf(decodedFields)) } } // If we want to know what keys are unused, compile that if len(unusedKeysVal) > 0 { /* unusedKeys := make([]string, 0, int(obj.Len())-len(usedKeys)) for _, elem := range obj.Elem { k := elem.Key() if _, ok := usedKeys[k]; !ok { unusedKeys = append(unusedKeys, k) } } if len(unusedKeys) == 0 { unusedKeys = nil } for _, v := range unusedKeysVal { v.Set(reflect.ValueOf(unusedKeys)) } */ } return nil }
func (d *decoder) decodeMap(name string, o *hcl.Object, result reflect.Value) error { if o.Type != hcl.ValueTypeObject { return fmt.Errorf("%s: not an object type for map (%v)", name, o.Type) } // If we have an interface, then we can address the interface, // but not the slice itself, so get the element but set the interface set := result if result.Kind() == reflect.Interface { result = result.Elem() } resultType := result.Type() resultElemType := resultType.Elem() resultKeyType := resultType.Key() if resultKeyType.Kind() != reflect.String { return fmt.Errorf( "%s: map must have string keys", name) } // Make a map if it is nil resultMap := result if result.IsNil() { resultMap = reflect.MakeMap( reflect.MapOf(resultKeyType, resultElemType)) } // Go through each element and decode it. for _, o := range o.Elem(false) { if o.Value == nil { continue } for _, o := range o.Elem(true) { // Make the field name fieldName := fmt.Sprintf("%s.%s", name, o.Key) // Get the key/value as reflection values key := reflect.ValueOf(o.Key) val := reflect.Indirect(reflect.New(resultElemType)) // If we have a pre-existing value in the map, use that oldVal := resultMap.MapIndex(key) if oldVal.IsValid() { val.Set(oldVal) } // Decode! if err := d.decode(fieldName, o, val); err != nil { return err } // Set the value on the map resultMap.SetMapIndex(key, val) } } // Set the final map if we can set.Set(resultMap) return nil }
func (d *decoder) decodeInterface(name string, o *hcl.Object, result reflect.Value) error { var set reflect.Value redecode := true switch o.Type { case hcl.ValueTypeObject: // If we're at the root or we're directly within a slice, then we // decode objects into map[string]interface{}, otherwise we decode // them into lists. if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice { var temp map[string]interface{} tempVal := reflect.ValueOf(temp) result := reflect.MakeMap( reflect.MapOf( reflect.TypeOf(""), tempVal.Type().Elem())) set = result } else { var temp []map[string]interface{} tempVal := reflect.ValueOf(temp) result := reflect.MakeSlice( reflect.SliceOf(tempVal.Type().Elem()), 0, int(o.Len())) set = result } case hcl.ValueTypeList: var temp []interface{} tempVal := reflect.ValueOf(temp) result := reflect.MakeSlice( reflect.SliceOf(tempVal.Type().Elem()), 0, 0) set = result case hcl.ValueTypeBool: var result bool set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) case hcl.ValueTypeFloat: var result float64 set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) case hcl.ValueTypeInt: var result int set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) case hcl.ValueTypeString: set = reflect.Indirect(reflect.New(reflect.TypeOf(""))) default: return fmt.Errorf( "%s: cannot decode into interface: %T", name, o) } // Set the result to what its supposed to be, then reset // result so we don't reflect into this method anymore. result.Set(set) if redecode { // Revisit the node so that we can use the newly instantiated // thing and populate it. if err := d.decode(name, o, result); err != nil { return err } } return nil }
func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([]*Provisioner, error) { pos := make([]*hclobj.Object, 0, int(os.Len())) // Accumulate all the actual provisioner configuration objects. We // have to iterate twice here: // // 1. The first iteration is of the list of `provisioner` blocks. // 2. The second iteration is of the dictionary within the // provisioner which will have only one element which is the // type of provisioner to use along with tis config. // // In JSON it looks kind of like this: // // [ // { // "shell": { // ... // } // } // ] // for _, o1 := range os.Elem(false) { for _, o2 := range o1.Elem(true) { switch o1.Type { case hclobj.ValueTypeList: for _, o3 := range o2.Elem(true) { pos = append(pos, o3) } case hclobj.ValueTypeObject: pos = append(pos, o2) } } } // Short-circuit if there are no items if len(pos) == 0 { return nil, nil } result := make([]*Provisioner, 0, len(pos)) for _, po := range pos { var config map[string]interface{} if err := hcl.DecodeObject(&config, po); err != nil { return nil, err } // Delete the "connection" section, handle seperately delete(config, "connection") rawConfig, err := NewRawConfig(config) if err != nil { return nil, err } // Check if we have a provisioner-level connection // block that overrides the resource-level var subConnInfo map[string]interface{} if o := po.Get("connection", false); o != nil { err := hcl.DecodeObject(&subConnInfo, o) if err != nil { return nil, err } } // Inherit from the resource connInfo any keys // that are not explicitly overriden. if connInfo != nil && subConnInfo != nil { for k, v := range connInfo { if _, ok := subConnInfo[k]; !ok { subConnInfo[k] = v } } } else if subConnInfo == nil { subConnInfo = connInfo } // Parse the connInfo connRaw, err := NewRawConfig(subConnInfo) if err != nil { return nil, err } result = append(result, &Provisioner{ Type: po.Key, RawConfig: rawConfig, ConnInfo: connRaw, }) } return result, nil }
// Given a handle to a HCL object, this recurses into the structure // and pulls out a list of resources. // // The resulting resources may not be unique, but each resource // represents exactly one resource definition in the HCL configuration. // We leave it up to another pass to merge them together. func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { var allTypes []*hclobj.Object // HCL object iteration is really nasty. Below is likely to make // no sense to anyone approaching this code. Luckily, it is very heavily // tested. If working on a bug fix or feature, we recommend writing a // test first then doing whatever you want to the code below. If you // break it, the tests will catch it. Likewise, if you change this, // MAKE SURE you write a test for your change, because its fairly impossible // to reason about this mess. // // Functionally, what the code does below is get the libucl.Objects // for all the TYPES, such as "aws_security_group". for _, o1 := range os.Elem(false) { // Iterate the inner to get the list of types for _, o2 := range o1.Elem(true) { // Iterate all of this type to get _all_ the types for _, o3 := range o2.Elem(false) { allTypes = append(allTypes, o3) } } } // Where all the results will go var result []*Resource // Now go over all the types and their children in order to get // all of the actual resources. for _, t := range allTypes { for _, obj := range t.Elem(true) { k := obj.Key var config map[string]interface{} if err := hcl.DecodeObject(&config, obj); err != nil { return nil, fmt.Errorf( "Error reading config for %s[%s]: %s", t.Key, k, err) } // Remove the fields we handle specially delete(config, "connection") delete(config, "count") delete(config, "depends_on") delete(config, "provisioner") delete(config, "provider") delete(config, "lifecycle") rawConfig, err := NewRawConfig(config) if err != nil { return nil, fmt.Errorf( "Error reading config for %s[%s]: %s", t.Key, k, err) } // If we have a count, then figure it out var count string = "1" if o := obj.Get("count", false); o != nil { err = hcl.DecodeObject(&count, o) if err != nil { return nil, fmt.Errorf( "Error parsing count for %s[%s]: %s", t.Key, k, err) } } countConfig, err := NewRawConfig(map[string]interface{}{ "count": count, }) if err != nil { return nil, err } countConfig.Key = "count" // If we have depends fields, then add those in var dependsOn []string if o := obj.Get("depends_on", false); o != nil { err := hcl.DecodeObject(&dependsOn, o) if err != nil { return nil, fmt.Errorf( "Error reading depends_on for %s[%s]: %s", t.Key, k, err) } } // If we have connection info, then parse those out var connInfo map[string]interface{} if o := obj.Get("connection", false); o != nil { err := hcl.DecodeObject(&connInfo, o) if err != nil { return nil, fmt.Errorf( "Error reading connection info for %s[%s]: %s", t.Key, k, err) } } // If we have provisioners, then parse those out var provisioners []*Provisioner if os := obj.Get("provisioner", false); os != nil { var err error provisioners, err = loadProvisionersHcl(os, connInfo) if err != nil { return nil, fmt.Errorf( "Error reading provisioners for %s[%s]: %s", t.Key, k, err) } } // If we have a provider, then parse it out var provider string if o := obj.Get("provider", false); o != nil { err := hcl.DecodeObject(&provider, o) if err != nil { return nil, fmt.Errorf( "Error reading provider for %s[%s]: %s", t.Key, k, err) } } // Check if the resource should be re-created before // destroying the existing instance var lifecycle ResourceLifecycle if o := obj.Get("lifecycle", false); o != nil { err = hcl.DecodeObject(&lifecycle, o) if err != nil { return nil, fmt.Errorf( "Error parsing lifecycle for %s[%s]: %s", t.Key, k, err) } } result = append(result, &Resource{ Name: k, Type: t.Key, RawCount: countConfig, RawConfig: rawConfig, Provisioners: provisioners, Provider: provider, DependsOn: dependsOn, Lifecycle: lifecycle, }) } } return result, nil }
// Given a handle to a HCL object, this recurses into the structure // and pulls out a list of modules. // // The resulting modules may not be unique, but each module // represents exactly one module definition in the HCL configuration. // We leave it up to another pass to merge them together. func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { var allNames []*hclobj.Object // See loadResourcesHcl for why this exists. Don't touch this. for _, o1 := range os.Elem(false) { // Iterate the inner to get the list of types for _, o2 := range o1.Elem(true) { // Iterate all of this type to get _all_ the types for _, o3 := range o2.Elem(false) { allNames = append(allNames, o3) } } } // Where all the results will go var result []*Module // Now go over all the types and their children in order to get // all of the actual resources. for _, obj := range allNames { k := obj.Key var config map[string]interface{} if err := hcl.DecodeObject(&config, obj); err != nil { return nil, fmt.Errorf( "Error reading config for %s: %s", k, err) } // Remove the fields we handle specially delete(config, "source") rawConfig, err := NewRawConfig(config) if err != nil { return nil, fmt.Errorf( "Error reading config for %s: %s", k, err) } // If we have a count, then figure it out var source string if o := obj.Get("source", false); o != nil { err = hcl.DecodeObject(&source, o) if err != nil { return nil, fmt.Errorf( "Error parsing source for %s: %s", k, err) } } result = append(result, &Module{ Name: k, Source: source, RawConfig: rawConfig, }) } return result, nil }