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