github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/terraform/plan.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "errors" 7 "fmt" 8 "io" 9 "log" 10 "sync" 11 12 "github.com/hashicorp/terraform/config/module" 13 ) 14 15 func init() { 16 gob.Register(make([]interface{}, 0)) 17 gob.Register(make([]map[string]interface{}, 0)) 18 gob.Register(make(map[string]interface{})) 19 gob.Register(make(map[string]string)) 20 } 21 22 // Plan represents a single Terraform execution plan, which contains 23 // all the information necessary to make an infrastructure change. 24 // 25 // A plan has to contain basically the entire state of the world 26 // necessary to make a change: the state, diff, config, backend config, etc. 27 // This is so that it can run alone without any other data. 28 type Plan struct { 29 Diff *Diff 30 Module *module.Tree 31 State *State 32 Vars map[string]interface{} 33 Targets []string 34 35 TerraformVersion string 36 ProviderSHA256s map[string][]byte 37 38 // Backend is the backend that this plan should use and store data with. 39 Backend *BackendState 40 41 once sync.Once 42 } 43 44 // Context returns a Context with the data encapsulated in this plan. 45 // 46 // The following fields in opts are overridden by the plan: Config, 47 // Diff, Variables. 48 // 49 // If State is not provided, it is set from the plan. If it _is_ provided, 50 // it must be Equal to the state stored in plan, but may have a newer 51 // serial. 52 func (p *Plan) Context(opts *ContextOpts) (*Context, error) { 53 var err error 54 opts, err = p.contextOpts(opts) 55 if err != nil { 56 return nil, err 57 } 58 return NewContext(opts) 59 } 60 61 // contextOpts mutates the given base ContextOpts in place to use input 62 // objects obtained from the receiving plan. 63 func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { 64 opts := base 65 66 opts.Diff = p.Diff 67 opts.Module = p.Module 68 opts.Targets = p.Targets 69 opts.ProviderSHA256s = p.ProviderSHA256s 70 71 if opts.State == nil { 72 opts.State = p.State 73 } else if !opts.State.Equal(p.State) { 74 // Even if we're overriding the state, it should be logically equal 75 // to what's in plan. The only valid change to have made by the time 76 // we get here is to have incremented the serial. 77 // 78 // Due to the fact that serialization may change the representation of 79 // the state, there is little chance that these aren't actually equal. 80 // Log the error condition for reference, but continue with the state 81 // we have. 82 log.Println("[WARNING] Plan state and ContextOpts state are not equal") 83 } 84 85 thisVersion := VersionString() 86 if p.TerraformVersion != "" && p.TerraformVersion != thisVersion { 87 return nil, fmt.Errorf( 88 "plan was created with a different version of Terraform (created with %s, but running %s)", 89 p.TerraformVersion, thisVersion, 90 ) 91 } 92 93 opts.Variables = make(map[string]interface{}) 94 for k, v := range p.Vars { 95 opts.Variables[k] = v 96 } 97 98 return opts, nil 99 } 100 101 func (p *Plan) String() string { 102 buf := new(bytes.Buffer) 103 buf.WriteString("DIFF:\n\n") 104 buf.WriteString(p.Diff.String()) 105 buf.WriteString("\n\nSTATE:\n\n") 106 buf.WriteString(p.State.String()) 107 return buf.String() 108 } 109 110 func (p *Plan) init() { 111 p.once.Do(func() { 112 if p.Diff == nil { 113 p.Diff = new(Diff) 114 p.Diff.init() 115 } 116 117 if p.State == nil { 118 p.State = new(State) 119 p.State.init() 120 } 121 122 if p.Vars == nil { 123 p.Vars = make(map[string]interface{}) 124 } 125 }) 126 } 127 128 // The format byte is prefixed into the plan file format so that we have 129 // the ability in the future to change the file format if we want for any 130 // reason. 131 const planFormatMagic = "tfplan" 132 const planFormatVersion byte = 2 133 134 // ReadPlan reads a plan structure out of a reader in the format that 135 // was written by WritePlan. 136 func ReadPlan(src io.Reader) (*Plan, error) { 137 var result *Plan 138 var err error 139 n := 0 140 141 // Verify the magic bytes 142 magic := make([]byte, len(planFormatMagic)) 143 for n < len(magic) { 144 n, err = src.Read(magic[n:]) 145 if err != nil { 146 return nil, fmt.Errorf("error while reading magic bytes: %s", err) 147 } 148 } 149 if string(magic) != planFormatMagic { 150 return nil, fmt.Errorf("not a valid plan file") 151 } 152 153 // Verify the version is something we can read 154 var formatByte [1]byte 155 n, err = src.Read(formatByte[:]) 156 if err != nil { 157 return nil, err 158 } 159 if n != len(formatByte) { 160 return nil, errors.New("failed to read plan version byte") 161 } 162 163 if formatByte[0] != planFormatVersion { 164 return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0]) 165 } 166 167 dec := gob.NewDecoder(src) 168 if err := dec.Decode(&result); err != nil { 169 return nil, err 170 } 171 172 return result, nil 173 } 174 175 // WritePlan writes a plan somewhere in a binary format. 176 func WritePlan(d *Plan, dst io.Writer) error { 177 // Write the magic bytes so we can determine the file format later 178 n, err := dst.Write([]byte(planFormatMagic)) 179 if err != nil { 180 return err 181 } 182 if n != len(planFormatMagic) { 183 return errors.New("failed to write plan format magic bytes") 184 } 185 186 // Write a version byte so we can iterate on version at some point 187 n, err = dst.Write([]byte{planFormatVersion}) 188 if err != nil { 189 return err 190 } 191 if n != 1 { 192 return errors.New("failed to write plan version byte") 193 } 194 195 return gob.NewEncoder(dst).Encode(d) 196 }