From 5dc37bafe6884209f6df198b1fc2b909e05cf341 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Mon, 15 Sep 2014 15:31:17 -0700 Subject: [PATCH] Adding the new state structures --- terraform/state.go | 351 +++++++++++------------------------------- terraform/state_v1.go | 279 +++++++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+), 258 deletions(-) create mode 100644 terraform/state_v1.go diff --git a/terraform/state.go b/terraform/state.go index 2e45a661f..281893e24 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -1,181 +1,121 @@ 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. +// managing. This is the latest format as of Terraform 0.3 type State struct { - Outputs map[string]string - Resources map[string]*ResourceState - Tainted map[string]struct{} + // Version is the protocol version. Currently only "1". + Version int `json:"version"` - once sync.Once + // Serial is incremented on any operation that modifies + // the State file. It is used to detect potentially conflicting + // updates. + Serial int64 `json:"serial"` + + // Modules contains all the modules in a breadth-first order + Modules []*ModuleState `json:"modules"` } -func (s *State) init() { - s.once.Do(func() { - if s.Resources == nil { - s.Resources = make(map[string]*ResourceState) - } +// ModuleState is used to track all the state relevant to a single +// module. Previous to Terraform 0.3, all state belonged to the "root" +// module. +type ModuleState struct { + // Path is the import path from the root module. Modules imports are + // always disjoint, so the path represents amodule tree + Path []string `json:"path"` - if s.Tainted == nil { - s.Tainted = make(map[string]struct{}) - } - }) + // Outputs declared by the module and maintained for each module + // even though only the root module technically needs to be kept. + // This allows operators to inspect values at the boundaries. + Outputs map[string]string `json:"outputs"` + + // Resources is a mapping of the logically named resource to + // the state of the resource. Each resource may actually have + // N instances underneath, although a user only needs to think + // about the 1:1 case. + Resources map[string]*ResourceState `json:"resources"` } -func (s *State) deepcopy() *State { - result := new(State) - result.init() - if s != nil { - for k, v := range s.Resources { - result.Resources[k] = v - } - for k, v := range s.Tainted { - result.Tainted[k] = v - } - } +// 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 used 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 `json:"type"` - return result + // 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 []string `json:"depends_on,omitempty"` + + // Instances is used to track all of the underlying instances + // have been created as part of this logical resource. In the + // standard case, there is only a single underlying instance. + // However, in pathological cases, it is possible for the number + // of instances to accumulate. The first instance in the list is + // the "primary" and the others should be removed on subsequent + // apply operations. + Instances []*InstanceState `json:"instances"` } -// prune is a helper that removes any empty IDs from the state -// and cleans it up in general. -func (s *State) prune() { - for k, v := range s.Resources { - if v.ID == "" { - delete(s.Resources, k) - } - } +// InstanceState is used to track the unique state information belonging +// to a given instance. +type InstanceState struct { + // A unique ID for this resource. This is opaque to Terraform + // and is only meant as a lookup mechanism for the providers. + ID string `json:"id"` + + // Tainted is used to mark a resource as existing but being in + // an unknown or errored state. Hence, it is 'tainted' and should + // be destroyed and replaced on the next fun. + Tainted bool `json:"tainted,omitempty"` + + // 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 `json:"attributes,omitempty"` + + // Ephemeral is used to store any state associated with this instance + // that is necessary for the Terraform run to complete, but is not + // persisted to a state file. + Ephemeral EphemeralState `json:"-"` } -// 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()) - - // Mark all the counts as not orphans. - for i := 0; i < r.Count; i++ { - delete(keys, fmt.Sprintf("%s.%d", r.Id(), i)) - } - } - - result := make([]string, 0, len(keys)) - for k, _ := range keys { - result = append(result, k) - } - - return result +// EphemeralState is used for transient state that is only kept in-memory +type EphemeralState struct { + // ConnInfo is used for the providers to export information which is + // used to connect to the resource for provisioning. For example, + // this could contain SSH or WinRM credentials. + ConnInfo map[string]string `json:"-"` } -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 = "" - } - - taintStr := "" - if _, ok := s.Tainted[k]; ok { - taintStr = " (tainted)" - } - - buf.WriteString(fmt.Sprintf("%s:%s\n", k, taintStr)) - buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) - - attrKeys := make([]string, 0, len(rs.Attributes)) - for ak, _ := range rs.Attributes { - if ak == "id" { - continue - } - - 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)) - } - } - } - - if len(s.Outputs) > 0 { - buf.WriteString("\nOutputs:\n\n") - - ks := make([]string, 0, len(s.Outputs)) - for k, _ := range s.Outputs { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - v := s.Outputs[k] - buf.WriteString(fmt.Sprintf("%s = %s\n", k, v)) - } - } - - return buf.String() -} - -// sensitiveState is used to store sensitive state information -// that should not be serialized. This is only used temporarily -// and is restored into the state. -type sensitiveState struct { - ConnInfo map[string]map[string]string - - once sync.Once -} - -func (s *sensitiveState) init() { - s.once.Do(func() { - s.ConnInfo = make(map[string]map[string]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 stateFormatMagic = "tfstate" -const stateFormatVersion 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) { @@ -258,108 +198,3 @@ func WriteState(d *State, dst io.Writer) error { return err } - -// 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 - - // ConnInfo is used for the providers to export information which is - // used to connect to the resource for provisioning. For example, - // this could contain SSH or WinRM credentials. - ConnInfo 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.NewRemoved { - delete(result.Attributes, k) - continue - } - if diff.NewComputed { - result.Attributes[k] = config.UnknownVariableValue - continue - } - - result.Attributes[k] = diff.New - } - } - - return &result -} - -func (s *ResourceState) GoString() string { - return fmt.Sprintf("*%#v", *s) -} - -// 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 -} diff --git a/terraform/state_v1.go b/terraform/state_v1.go new file mode 100644 index 000000000..67ae2c47c --- /dev/null +++ b/terraform/state_v1.go @@ -0,0 +1,279 @@ +package terraform + +import ( + "bytes" + "fmt" + "sort" + "sync" + + "github.com/hashicorp/terraform/config" +) + +// StateV1 is used to represent the state of Terraform files before +// 0.3. It is automatically upgraded to a modern State representation +// on start. +type StateV1 struct { + Outputs map[string]string + Resources map[string]*ResourceState + Tainted map[string]struct{} + + once sync.Once +} + +func (s *State) init() { + s.once.Do(func() { + if s.Resources == nil { + s.Resources = make(map[string]*ResourceState) + } + + if s.Tainted == nil { + s.Tainted = make(map[string]struct{}) + } + }) +} + +func (s *State) deepcopy() *State { + result := new(State) + result.init() + if s != nil { + for k, v := range s.Resources { + result.Resources[k] = v + } + for k, v := range s.Tainted { + result.Tainted[k] = v + } + } + + return result +} + +// prune is a helper that removes any empty IDs from the state +// and cleans it up in general. +func (s *State) prune() { + for k, v := range s.Resources { + if v.ID == "" { + delete(s.Resources, k) + } + } +} + +// 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()) + + // Mark all the counts as not orphans. + for i := 0; i < r.Count; i++ { + delete(keys, fmt.Sprintf("%s.%d", r.Id(), i)) + } + } + + 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 = "" + } + + taintStr := "" + if _, ok := s.Tainted[k]; ok { + taintStr = " (tainted)" + } + + buf.WriteString(fmt.Sprintf("%s:%s\n", k, taintStr)) + buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) + + attrKeys := make([]string, 0, len(rs.Attributes)) + for ak, _ := range rs.Attributes { + if ak == "id" { + continue + } + + 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)) + } + } + } + + if len(s.Outputs) > 0 { + buf.WriteString("\nOutputs:\n\n") + + ks := make([]string, 0, len(s.Outputs)) + for k, _ := range s.Outputs { + ks = append(ks, k) + } + sort.Strings(ks) + + for _, k := range ks { + v := s.Outputs[k] + buf.WriteString(fmt.Sprintf("%s = %s\n", k, v)) + } + } + + return buf.String() +} + +// sensitiveState is used to store sensitive state information +// that should not be serialized. This is only used temporarily +// and is restored into the state. +type sensitiveState struct { + ConnInfo map[string]map[string]string + + once sync.Once +} + +func (s *sensitiveState) init() { + s.once.Do(func() { + s.ConnInfo = make(map[string]map[string]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 stateFormatMagic = "tfstate" +const stateFormatVersion byte = 1 + +/// 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 ResourceStateV1 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 + + // ConnInfo is used for the providers to export information which is + // used to connect to the resource for provisioning. For example, + // this could contain SSH or WinRM credentials. + ConnInfo 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.NewRemoved { + delete(result.Attributes, k) + continue + } + if diff.NewComputed { + result.Attributes[k] = config.UnknownVariableValue + continue + } + + result.Attributes[k] = diff.New + } + } + + return &result +} + +func (s *ResourceState) GoString() string { + return fmt.Sprintf("*%#v", *s) +} + +// 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 +}