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