github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 TerraformVersion string 35 ProviderSHA256s map[string][]byte 36 37 // Backend is the backend that this plan should use and store data with. 38 Backend *BackendState 39 40 once sync.Once 41 } 42 43 // Context returns a Context with the data encapsulated in this plan. 44 // 45 // The following fields in opts are overridden by the plan: Config, 46 // Diff, State, Variables. 47 func (p *Plan) Context(opts *ContextOpts) (*Context, error) { 48 var err error 49 opts, err = p.contextOpts(opts) 50 if err != nil { 51 return nil, err 52 } 53 return NewContext(opts) 54 } 55 56 // contextOpts mutates the given base ContextOpts in place to use input 57 // objects obtained from the receiving plan. 58 func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { 59 opts := base 60 61 opts.Diff = p.Diff 62 opts.Module = p.Module 63 opts.State = p.State 64 opts.Targets = p.Targets 65 66 opts.ProviderSHA256s = p.ProviderSHA256s 67 68 thisVersion := VersionString() 69 if p.TerraformVersion != "" && p.TerraformVersion != thisVersion { 70 return nil, fmt.Errorf( 71 "plan was created with a different version of Terraform (created with %s, but running %s)", 72 p.TerraformVersion, thisVersion, 73 ) 74 } 75 76 opts.Variables = make(map[string]interface{}) 77 for k, v := range p.Vars { 78 opts.Variables[k] = v 79 } 80 81 return opts, nil 82 } 83 84 func (p *Plan) String() string { 85 buf := new(bytes.Buffer) 86 buf.WriteString("DIFF:\n\n") 87 buf.WriteString(p.Diff.String()) 88 buf.WriteString("\n\nSTATE:\n\n") 89 buf.WriteString(p.State.String()) 90 return buf.String() 91 } 92 93 func (p *Plan) init() { 94 p.once.Do(func() { 95 if p.Diff == nil { 96 p.Diff = new(Diff) 97 p.Diff.init() 98 } 99 100 if p.State == nil { 101 p.State = new(State) 102 p.State.init() 103 } 104 105 if p.Vars == nil { 106 p.Vars = make(map[string]interface{}) 107 } 108 }) 109 } 110 111 // The format byte is prefixed into the plan file format so that we have 112 // the ability in the future to change the file format if we want for any 113 // reason. 114 const planFormatMagic = "tfplan" 115 const planFormatVersion byte = 2 116 117 // ReadPlan reads a plan structure out of a reader in the format that 118 // was written by WritePlan. 119 func ReadPlan(src io.Reader) (*Plan, error) { 120 var result *Plan 121 var err error 122 n := 0 123 124 // Verify the magic bytes 125 magic := make([]byte, len(planFormatMagic)) 126 for n < len(magic) { 127 n, err = src.Read(magic[n:]) 128 if err != nil { 129 return nil, fmt.Errorf("error while reading magic bytes: %s", err) 130 } 131 } 132 if string(magic) != planFormatMagic { 133 return nil, fmt.Errorf("not a valid plan file") 134 } 135 136 // Verify the version is something we can read 137 var formatByte [1]byte 138 n, err = src.Read(formatByte[:]) 139 if err != nil { 140 return nil, err 141 } 142 if n != len(formatByte) { 143 return nil, errors.New("failed to read plan version byte") 144 } 145 146 if formatByte[0] != planFormatVersion { 147 return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0]) 148 } 149 150 dec := gob.NewDecoder(src) 151 if err := dec.Decode(&result); err != nil { 152 return nil, err 153 } 154 155 return result, nil 156 } 157 158 // WritePlan writes a plan somewhere in a binary format. 159 func WritePlan(d *Plan, dst io.Writer) error { 160 // Write the magic bytes so we can determine the file format later 161 n, err := dst.Write([]byte(planFormatMagic)) 162 if err != nil { 163 return err 164 } 165 if n != len(planFormatMagic) { 166 return errors.New("failed to write plan format magic bytes") 167 } 168 169 // Write a version byte so we can iterate on version at some point 170 n, err = dst.Write([]byte{planFormatVersion}) 171 if err != nil { 172 return err 173 } 174 if n != 1 { 175 return errors.New("failed to write plan version byte") 176 } 177 178 return gob.NewEncoder(dst).Encode(d) 179 }