github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/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 describes the resource actions that must be taken when this 30 // plan is applied. 31 Diff *Diff 32 33 // Module represents the entire configuration that was present when this 34 // plan was created. 35 Module *module.Tree 36 37 // State is the Terraform state that was current when this plan was 38 // created. 39 // 40 // It is not allowed to apply a plan that has a stale state, since its 41 // diff could be outdated. 42 State *State 43 44 // Vars retains the variables that were set when creating the plan, so 45 // that the same variables can be applied during apply. 46 Vars map[string]interface{} 47 48 // Targets, if non-empty, contains a set of resource address strings that 49 // identify graph nodes that were selected as targets for plan. 50 // 51 // When targets are set, any graph node that is not directly targeted or 52 // indirectly targeted via dependencies is excluded from the graph. 53 Targets []string 54 55 // TerraformVersion is the version of Terraform that was used to create 56 // this plan. 57 // 58 // It is not allowed to apply a plan created with a different version of 59 // Terraform, since the other fields of this structure may be interpreted 60 // in different ways between versions. 61 TerraformVersion string 62 63 // ProviderSHA256s is a map giving the SHA256 hashes of the exact binaries 64 // used as plugins for each provider during plan. 65 // 66 // These must match between plan and apply to ensure that the diff is 67 // correctly interpreted, since different provider versions may have 68 // different attributes or attribute value constraints. 69 ProviderSHA256s map[string][]byte 70 71 // Backend is the backend that this plan should use and store data with. 72 Backend *BackendState 73 74 once sync.Once 75 } 76 77 // Context returns a Context with the data encapsulated in this plan. 78 // 79 // The following fields in opts are overridden by the plan: Config, 80 // Diff, Variables. 81 // 82 // If State is not provided, it is set from the plan. If it _is_ provided, 83 // it must be Equal to the state stored in plan, but may have a newer 84 // serial. 85 func (p *Plan) Context(opts *ContextOpts) (*Context, error) { 86 var err error 87 opts, err = p.contextOpts(opts) 88 if err != nil { 89 return nil, err 90 } 91 return NewContext(opts) 92 } 93 94 // contextOpts mutates the given base ContextOpts in place to use input 95 // objects obtained from the receiving plan. 96 func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { 97 opts := base 98 99 opts.Diff = p.Diff 100 opts.Module = p.Module 101 opts.Targets = p.Targets 102 opts.ProviderSHA256s = p.ProviderSHA256s 103 104 if opts.State == nil { 105 opts.State = p.State 106 } else if !opts.State.Equal(p.State) { 107 // Even if we're overriding the state, it should be logically equal 108 // to what's in plan. The only valid change to have made by the time 109 // we get here is to have incremented the serial. 110 // 111 // Due to the fact that serialization may change the representation of 112 // the state, there is little chance that these aren't actually equal. 113 // Log the error condition for reference, but continue with the state 114 // we have. 115 log.Println("[WARNING] Plan state and ContextOpts state are not equal") 116 } 117 118 thisVersion := VersionString() 119 if p.TerraformVersion != "" && p.TerraformVersion != thisVersion { 120 return nil, fmt.Errorf( 121 "plan was created with a different version of Terraform (created with %s, but running %s)", 122 p.TerraformVersion, thisVersion, 123 ) 124 } 125 126 opts.Variables = make(map[string]interface{}) 127 for k, v := range p.Vars { 128 opts.Variables[k] = v 129 } 130 131 return opts, nil 132 } 133 134 func (p *Plan) String() string { 135 buf := new(bytes.Buffer) 136 buf.WriteString("DIFF:\n\n") 137 buf.WriteString(p.Diff.String()) 138 buf.WriteString("\n\nSTATE:\n\n") 139 buf.WriteString(p.State.String()) 140 return buf.String() 141 } 142 143 func (p *Plan) init() { 144 p.once.Do(func() { 145 if p.Diff == nil { 146 p.Diff = new(Diff) 147 p.Diff.init() 148 } 149 150 if p.State == nil { 151 p.State = new(State) 152 p.State.init() 153 } 154 155 if p.Vars == nil { 156 p.Vars = make(map[string]interface{}) 157 } 158 }) 159 } 160 161 // The format byte is prefixed into the plan file format so that we have 162 // the ability in the future to change the file format if we want for any 163 // reason. 164 const planFormatMagic = "tfplan" 165 const planFormatVersion byte = 2 166 167 // ReadPlan reads a plan structure out of a reader in the format that 168 // was written by WritePlan. 169 func ReadPlan(src io.Reader) (*Plan, error) { 170 var result *Plan 171 var err error 172 n := 0 173 174 // Verify the magic bytes 175 magic := make([]byte, len(planFormatMagic)) 176 for n < len(magic) { 177 n, err = src.Read(magic[n:]) 178 if err != nil { 179 return nil, fmt.Errorf("error while reading magic bytes: %s", err) 180 } 181 } 182 if string(magic) != planFormatMagic { 183 return nil, fmt.Errorf("not a valid plan file") 184 } 185 186 // Verify the version is something we can read 187 var formatByte [1]byte 188 n, err = src.Read(formatByte[:]) 189 if err != nil { 190 return nil, err 191 } 192 if n != len(formatByte) { 193 return nil, errors.New("failed to read plan version byte") 194 } 195 196 if formatByte[0] != planFormatVersion { 197 return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0]) 198 } 199 200 dec := gob.NewDecoder(src) 201 if err := dec.Decode(&result); err != nil { 202 return nil, err 203 } 204 205 return result, nil 206 } 207 208 // WritePlan writes a plan somewhere in a binary format. 209 func WritePlan(d *Plan, dst io.Writer) error { 210 // Write the magic bytes so we can determine the file format later 211 n, err := dst.Write([]byte(planFormatMagic)) 212 if err != nil { 213 return err 214 } 215 if n != len(planFormatMagic) { 216 return errors.New("failed to write plan format magic bytes") 217 } 218 219 // Write a version byte so we can iterate on version at some point 220 n, err = dst.Write([]byte{planFormatVersion}) 221 if err != nil { 222 return err 223 } 224 if n != 1 { 225 return errors.New("failed to write plan version byte") 226 } 227 228 return gob.NewEncoder(dst).Encode(d) 229 }