github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/terraform/plan.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "errors" 7 "fmt" 8 "io" 9 "sync" 10 11 "github.com/hashicorp/terraform/config/module" 12 ) 13 14 func init() { 15 gob.Register(make([]interface{}, 0)) 16 gob.Register(make([]map[string]interface{}, 0)) 17 gob.Register(make(map[string]interface{})) 18 gob.Register(make(map[string]string)) 19 } 20 21 // Plan represents a single Terraform execution plan, which contains 22 // all the information necessary to make an infrastructure change. 23 type Plan struct { 24 Diff *Diff 25 Module *module.Tree 26 State *State 27 Vars map[string]string 28 29 once sync.Once 30 } 31 32 // Context returns a Context with the data encapsulated in this plan. 33 // 34 // The following fields in opts are overridden by the plan: Config, 35 // Diff, State, Variables. 36 func (p *Plan) Context(opts *ContextOpts) *Context { 37 opts.Diff = p.Diff 38 opts.Module = p.Module 39 opts.State = p.State 40 opts.Variables = p.Vars 41 return NewContext(opts) 42 } 43 44 func (p *Plan) String() string { 45 buf := new(bytes.Buffer) 46 buf.WriteString("DIFF:\n\n") 47 buf.WriteString(p.Diff.String()) 48 buf.WriteString("\n\nSTATE:\n\n") 49 buf.WriteString(p.State.String()) 50 return buf.String() 51 } 52 53 func (p *Plan) init() { 54 p.once.Do(func() { 55 if p.Diff == nil { 56 p.Diff = new(Diff) 57 p.Diff.init() 58 } 59 60 if p.State == nil { 61 p.State = new(State) 62 p.State.init() 63 } 64 65 if p.Vars == nil { 66 p.Vars = make(map[string]string) 67 } 68 }) 69 } 70 71 // The format byte is prefixed into the plan file format so that we have 72 // the ability in the future to change the file format if we want for any 73 // reason. 74 const planFormatMagic = "tfplan" 75 const planFormatVersion byte = 1 76 77 // ReadPlan reads a plan structure out of a reader in the format that 78 // was written by WritePlan. 79 func ReadPlan(src io.Reader) (*Plan, error) { 80 var result *Plan 81 var err error 82 n := 0 83 84 // Verify the magic bytes 85 magic := make([]byte, len(planFormatMagic)) 86 for n < len(magic) { 87 n, err = src.Read(magic[n:]) 88 if err != nil { 89 return nil, fmt.Errorf("error while reading magic bytes: %s", err) 90 } 91 } 92 if string(magic) != planFormatMagic { 93 return nil, fmt.Errorf("not a valid plan file") 94 } 95 96 // Verify the version is something we can read 97 var formatByte [1]byte 98 n, err = src.Read(formatByte[:]) 99 if err != nil { 100 return nil, err 101 } 102 if n != len(formatByte) { 103 return nil, errors.New("failed to read plan version byte") 104 } 105 106 if formatByte[0] != planFormatVersion { 107 return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0]) 108 } 109 110 dec := gob.NewDecoder(src) 111 if err := dec.Decode(&result); err != nil { 112 return nil, err 113 } 114 115 return result, nil 116 } 117 118 // WritePlan writes a plan somewhere in a binary format. 119 func WritePlan(d *Plan, dst io.Writer) error { 120 // Write the magic bytes so we can determine the file format later 121 n, err := dst.Write([]byte(planFormatMagic)) 122 if err != nil { 123 return err 124 } 125 if n != len(planFormatMagic) { 126 return errors.New("failed to write plan format magic bytes") 127 } 128 129 // Write a version byte so we can iterate on version at some point 130 n, err = dst.Write([]byte{planFormatVersion}) 131 if err != nil { 132 return err 133 } 134 if n != 1 { 135 return errors.New("failed to write plan version byte") 136 } 137 138 return gob.NewEncoder(dst).Encode(d) 139 }