package terraform import ( "bytes" "encoding/gob" "errors" "fmt" "io" "sort" "sync" "github.com/hashicorp/terraform/config" ) // State keeps track of a snapshot state-of-the-world that Terraform // can use to keep track of what real world resources it is actually // managing. type State struct { Resources map[string]*ResourceState once sync.Once } func (s *State) init() { s.once.Do(func() { s.Resources = make(map[string]*ResourceState) }) } // Orphans returns a list of keys of resources that are in the State // but aren't present in the configuration itself. Hence, these keys // represent the state of resources that are orphans. func (s *State) Orphans(c *config.Config) []string { keys := make(map[string]struct{}) for k, _ := range s.Resources { keys[k] = struct{}{} } for _, r := range c.Resources { delete(keys, r.Id()) // If there is only one of this instance, then we alias that // to the ".0" version as well so that it can count if r.Count == 1 { delete(keys, r.Id()+".0") } } result := make([]string, 0, len(keys)) for k, _ := range keys { result = append(result, k) } return result } func (s *State) String() string { if len(s.Resources) == 0 { return "" } var buf bytes.Buffer names := make([]string, 0, len(s.Resources)) for name, _ := range s.Resources { names = append(names, name) } sort.Strings(names) for _, k := range names { rs := s.Resources[k] id := rs.ID if id == "" { id = "" } buf.WriteString(fmt.Sprintf("%s:\n", k)) buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) attrKeys := make([]string, 0, len(rs.Attributes)) for ak, _ := range rs.Attributes { attrKeys = append(attrKeys, ak) } sort.Strings(attrKeys) for _, ak := range attrKeys { av := rs.Attributes[ak] buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av)) } if len(rs.Dependencies) > 0 { buf.WriteString(fmt.Sprintf("\n Dependencies:\n")) for _, dep := range rs.Dependencies { buf.WriteString(fmt.Sprintf(" %s\n", dep.ID)) } } } return buf.String() } // The format byte is prefixed into the state file format so that we have // the ability in the future to change the file format if we want for any // reason. const stateFormatByte byte = 1 // ReadState reads a state structure out of a reader in the format that // was written by WriteState. func ReadState(src io.Reader) (*State, error) { var result *State var formatByte [1]byte n, err := src.Read(formatByte[:]) if err != nil { return nil, err } if n != len(formatByte) { return nil, errors.New("failed to read state version byte") } if formatByte[0] != stateFormatByte { return nil, fmt.Errorf("unknown state file version: %d", formatByte[0]) } dec := gob.NewDecoder(src) if err := dec.Decode(&result); err != nil { return nil, err } return result, nil } // WriteState writes a state somewhere in a binary format. func WriteState(d *State, dst io.Writer) error { n, err := dst.Write([]byte{stateFormatByte}) if err != nil { return err } if n != 1 { return errors.New("failed to write state version byte") } return gob.NewEncoder(dst).Encode(d) } // ResourceState holds the state of a resource that is used so that // a provider can find and manage an existing resource as well as for // storing attributes that are uesd to populate variables of child // resources. // // Attributes has attributes about the created resource that are // queryable in interpolation: "${type.id.attr}" // // Extra is just extra data that a provider can return that we store // for later, but is not exposed in any way to the user. type ResourceState struct { // This is filled in and managed by Terraform, and is the resource // type itself such as "mycloud_instance". If a resource provider sets // this value, it won't be persisted. Type string // The attributes below are all meant to be filled in by the // resource providers themselves. Documentation for each are above // each element. // A unique ID for this resource. This is opaque to Terraform // and is only meant as a lookup mechanism for the providers. ID string // Attributes are basic information about the resource. Any keys here // are accessible in variable format within Terraform configurations: // ${resourcetype.name.attribute}. Attributes map[string]string // Extra information that the provider can store about a resource. // This data is opaque, never shown to the user, and is sent back to // the provider as-is for whatever purpose appropriate. Extra map[string]interface{} // Dependencies are a list of things that this resource relies on // existing to remain intact. For example: an AWS instance might // depend on a subnet (which itself might depend on a VPC, and so // on). // // Terraform uses this information to build valid destruction // orders and to warn the user if they're destroying a resource that // another resource depends on. // // Things can be put into this list that may not be managed by // Terraform. If Terraform doesn't find a matching ID in the // overall state, then it assumes it isn't managed and doesn't // worry about it. Dependencies []ResourceDependency } // MergeDiff takes a ResourceDiff and merges the attributes into // this resource state in order to generate a new state. This new // state can be used to provide updated attribute lookups for // variable interpolation. // // If the diff attribute requires computing the value, and hence // won't be available until apply, the value is replaced with the // computeID. func (s *ResourceState) MergeDiff(d *ResourceDiff) *ResourceState { var result ResourceState if s != nil { result = *s } result.Attributes = make(map[string]string) if s != nil { for k, v := range s.Attributes { result.Attributes[k] = v } } if d != nil { for k, diff := range d.Attributes { if diff.NewComputed { result.Attributes[k] = config.UnknownVariableValue continue } result.Attributes[k] = diff.New } } return &result } // ResourceDependency maps a resource to another resource that it // depends on to remain intact and uncorrupted. type ResourceDependency struct { // ID of the resource that we depend on. This ID should map // directly to another ResourceState's ID. ID string }