github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/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 // PlanOpts are the options used to generate an execution plan for 22 // Terraform. 23 type PlanOpts struct { 24 // If set to true, then the generated plan will destroy all resources 25 // that are created. Otherwise, it will move towards the desired state 26 // specified in the configuration. 27 Destroy bool 28 } 29 30 // Plan represents a single Terraform execution plan, which contains 31 // all the information necessary to make an infrastructure change. 32 type Plan struct { 33 Diff *Diff 34 Module *module.Tree 35 State *State 36 Vars map[string]string 37 38 once sync.Once 39 } 40 41 // Context returns a Context with the data encapsulated in this plan. 42 // 43 // The following fields in opts are overridden by the plan: Config, 44 // Diff, State, Variables. 45 func (p *Plan) Context(opts *ContextOpts) *Context { 46 opts.Diff = p.Diff 47 opts.Module = p.Module 48 opts.State = p.State 49 opts.Variables = p.Vars 50 return NewContext(opts) 51 } 52 53 func (p *Plan) String() string { 54 buf := new(bytes.Buffer) 55 buf.WriteString("DIFF:\n\n") 56 buf.WriteString(p.Diff.String()) 57 buf.WriteString("\n\nSTATE:\n\n") 58 buf.WriteString(p.State.String()) 59 return buf.String() 60 } 61 62 func (p *Plan) init() { 63 p.once.Do(func() { 64 if p.Diff == nil { 65 p.Diff = new(Diff) 66 p.Diff.init() 67 } 68 69 if p.State == nil { 70 p.State = new(State) 71 p.State.init() 72 } 73 74 if p.Vars == nil { 75 p.Vars = make(map[string]string) 76 } 77 }) 78 } 79 80 // The format byte is prefixed into the plan file format so that we have 81 // the ability in the future to change the file format if we want for any 82 // reason. 83 const planFormatMagic = "tfplan" 84 const planFormatVersion byte = 1 85 86 // ReadPlan reads a plan structure out of a reader in the format that 87 // was written by WritePlan. 88 func ReadPlan(src io.Reader) (*Plan, error) { 89 var result *Plan 90 var err error 91 n := 0 92 93 // Verify the magic bytes 94 magic := make([]byte, len(planFormatMagic)) 95 for n < len(magic) { 96 n, err = src.Read(magic[n:]) 97 if err != nil { 98 return nil, fmt.Errorf("error while reading magic bytes: %s", err) 99 } 100 } 101 if string(magic) != planFormatMagic { 102 return nil, fmt.Errorf("not a valid plan file") 103 } 104 105 // Verify the version is something we can read 106 var formatByte [1]byte 107 n, err = src.Read(formatByte[:]) 108 if err != nil { 109 return nil, err 110 } 111 if n != len(formatByte) { 112 return nil, errors.New("failed to read plan version byte") 113 } 114 115 if formatByte[0] != planFormatVersion { 116 return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0]) 117 } 118 119 dec := gob.NewDecoder(src) 120 if err := dec.Decode(&result); err != nil { 121 return nil, err 122 } 123 124 return result, nil 125 } 126 127 // WritePlan writes a plan somewhere in a binary format. 128 func WritePlan(d *Plan, dst io.Writer) error { 129 // Write the magic bytes so we can determine the file format later 130 n, err := dst.Write([]byte(planFormatMagic)) 131 if err != nil { 132 return err 133 } 134 if n != len(planFormatMagic) { 135 return errors.New("failed to write plan format magic bytes") 136 } 137 138 // Write a version byte so we can iterate on version at some point 139 n, err = dst.Write([]byte{planFormatVersion}) 140 if err != nil { 141 return err 142 } 143 if n != 1 { 144 return errors.New("failed to write plan version byte") 145 } 146 147 return gob.NewEncoder(dst).Encode(d) 148 }