github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/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]interface{} 28 Targets []string 29 30 once sync.Once 31 } 32 33 // Context returns a Context with the data encapsulated in this plan. 34 // 35 // The following fields in opts are overridden by the plan: Config, 36 // Diff, State, Variables. 37 func (p *Plan) Context(opts *ContextOpts) (*Context, error) { 38 opts.Diff = p.Diff 39 opts.Module = p.Module 40 opts.State = p.State 41 opts.Targets = p.Targets 42 43 opts.Variables = make(map[string]interface{}) 44 for k, v := range p.Vars { 45 opts.Variables[k] = v 46 } 47 48 return NewContext(opts) 49 } 50 51 func (p *Plan) String() string { 52 buf := new(bytes.Buffer) 53 buf.WriteString("DIFF:\n\n") 54 buf.WriteString(p.Diff.String()) 55 buf.WriteString("\n\nSTATE:\n\n") 56 buf.WriteString(p.State.String()) 57 return buf.String() 58 } 59 60 func (p *Plan) init() { 61 p.once.Do(func() { 62 if p.Diff == nil { 63 p.Diff = new(Diff) 64 p.Diff.init() 65 } 66 67 if p.State == nil { 68 p.State = new(State) 69 p.State.init() 70 } 71 72 if p.Vars == nil { 73 p.Vars = make(map[string]interface{}) 74 } 75 }) 76 } 77 78 // The format byte is prefixed into the plan file format so that we have 79 // the ability in the future to change the file format if we want for any 80 // reason. 81 const planFormatMagic = "tfplan" 82 const planFormatVersion byte = 1 83 84 // ReadPlan reads a plan structure out of a reader in the format that 85 // was written by WritePlan. 86 func ReadPlan(src io.Reader) (*Plan, error) { 87 var result *Plan 88 var err error 89 n := 0 90 91 // Verify the magic bytes 92 magic := make([]byte, len(planFormatMagic)) 93 for n < len(magic) { 94 n, err = src.Read(magic[n:]) 95 if err != nil { 96 return nil, fmt.Errorf("error while reading magic bytes: %s", err) 97 } 98 } 99 if string(magic) != planFormatMagic { 100 return nil, fmt.Errorf("not a valid plan file") 101 } 102 103 // Verify the version is something we can read 104 var formatByte [1]byte 105 n, err = src.Read(formatByte[:]) 106 if err != nil { 107 return nil, err 108 } 109 if n != len(formatByte) { 110 return nil, errors.New("failed to read plan version byte") 111 } 112 113 if formatByte[0] != planFormatVersion { 114 return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0]) 115 } 116 117 dec := gob.NewDecoder(src) 118 if err := dec.Decode(&result); err != nil { 119 return nil, err 120 } 121 122 return result, nil 123 } 124 125 // WritePlan writes a plan somewhere in a binary format. 126 func WritePlan(d *Plan, dst io.Writer) error { 127 // Write the magic bytes so we can determine the file format later 128 n, err := dst.Write([]byte(planFormatMagic)) 129 if err != nil { 130 return err 131 } 132 if n != len(planFormatMagic) { 133 return errors.New("failed to write plan format magic bytes") 134 } 135 136 // Write a version byte so we can iterate on version at some point 137 n, err = dst.Write([]byte{planFormatVersion}) 138 if err != nil { 139 return err 140 } 141 if n != 1 { 142 return errors.New("failed to write plan version byte") 143 } 144 145 return gob.NewEncoder(dst).Encode(d) 146 }