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