github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/packer/provisioner.go (about) 1 package packer 2 3 import ( 4 "fmt" 5 "log" 6 "sync" 7 "time" 8 ) 9 10 // A provisioner is responsible for installing and configuring software 11 // on a machine prior to building the actual image. 12 type Provisioner interface { 13 // Prepare is called with a set of configurations to setup the 14 // internal state of the provisioner. The multiple configurations 15 // should be merged in some sane way. 16 Prepare(...interface{}) error 17 18 // Provision is called to actually provision the machine. A UI is 19 // given to communicate with the user, and a communicator is given that 20 // is guaranteed to be connected to some machine so that provisioning 21 // can be done. 22 Provision(Ui, Communicator) error 23 24 // Cancel is called to cancel the provisioning. This is usually called 25 // while Provision is still being called. The Provisioner should act 26 // to stop its execution as quickly as possible in a race-free way. 27 Cancel() 28 } 29 30 // A HookedProvisioner represents a provisioner and information describing it 31 type HookedProvisioner struct { 32 Provisioner Provisioner 33 Config interface{} 34 TypeName string 35 } 36 37 // A Hook implementation that runs the given provisioners. 38 type ProvisionHook struct { 39 // The provisioners to run as part of the hook. These should already 40 // be prepared (by calling Prepare) at some earlier stage. 41 Provisioners []*HookedProvisioner 42 43 lock sync.Mutex 44 runningProvisioner Provisioner 45 } 46 47 // Runs the provisioners in order. 48 func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interface{}) error { 49 // Shortcut 50 if len(h.Provisioners) == 0 { 51 return nil 52 } 53 54 if comm == nil { 55 return fmt.Errorf( 56 "No communicator found for provisioners! This is usually because the\n" + 57 "`communicator` config was set to \"none\". If you have any provisioners\n" + 58 "then a communicator is required. Please fix this to continue.") 59 } 60 61 defer func() { 62 h.lock.Lock() 63 defer h.lock.Unlock() 64 65 h.runningProvisioner = nil 66 }() 67 68 for _, p := range h.Provisioners { 69 h.lock.Lock() 70 h.runningProvisioner = p.Provisioner 71 h.lock.Unlock() 72 73 ts := CheckpointReporter.AddSpan(p.TypeName, "provisioner", p.Config) 74 75 err := p.Provisioner.Provision(ui, comm) 76 77 ts.End(err) 78 if err != nil { 79 return err 80 } 81 } 82 83 return nil 84 } 85 86 // Cancels the provisioners that are still running. 87 func (h *ProvisionHook) Cancel() { 88 h.lock.Lock() 89 defer h.lock.Unlock() 90 91 if h.runningProvisioner != nil { 92 h.runningProvisioner.Cancel() 93 } 94 } 95 96 // PausedProvisioner is a Provisioner implementation that pauses before 97 // the provisioner is actually run. 98 type PausedProvisioner struct { 99 PauseBefore time.Duration 100 Provisioner Provisioner 101 102 cancelCh chan struct{} 103 doneCh chan struct{} 104 lock sync.Mutex 105 } 106 107 func (p *PausedProvisioner) Prepare(raws ...interface{}) error { 108 return p.Provisioner.Prepare(raws...) 109 } 110 111 func (p *PausedProvisioner) Provision(ui Ui, comm Communicator) error { 112 p.lock.Lock() 113 cancelCh := make(chan struct{}) 114 p.cancelCh = cancelCh 115 116 // Setup the done channel, which is trigger when we're done 117 doneCh := make(chan struct{}) 118 defer close(doneCh) 119 p.doneCh = doneCh 120 p.lock.Unlock() 121 122 defer func() { 123 p.lock.Lock() 124 defer p.lock.Unlock() 125 if p.cancelCh == cancelCh { 126 p.cancelCh = nil 127 } 128 if p.doneCh == doneCh { 129 p.doneCh = nil 130 } 131 }() 132 133 // Use a select to determine if we get cancelled during the wait 134 ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore)) 135 select { 136 case <-time.After(p.PauseBefore): 137 case <-cancelCh: 138 return nil 139 } 140 141 provDoneCh := make(chan error, 1) 142 go p.provision(provDoneCh, ui, comm) 143 144 select { 145 case err := <-provDoneCh: 146 return err 147 case <-cancelCh: 148 p.Provisioner.Cancel() 149 return <-provDoneCh 150 } 151 } 152 153 func (p *PausedProvisioner) Cancel() { 154 var doneCh chan struct{} 155 156 p.lock.Lock() 157 if p.cancelCh != nil { 158 close(p.cancelCh) 159 p.cancelCh = nil 160 } 161 if p.doneCh != nil { 162 doneCh = p.doneCh 163 } 164 p.lock.Unlock() 165 166 <-doneCh 167 } 168 169 func (p *PausedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) { 170 result <- p.Provisioner.Provision(ui, comm) 171 } 172 173 // DebuggedProvisioner is a Provisioner implementation that waits until a key 174 // press before the provisioner is actually run. 175 type DebuggedProvisioner struct { 176 Provisioner Provisioner 177 178 cancelCh chan struct{} 179 doneCh chan struct{} 180 lock sync.Mutex 181 } 182 183 func (p *DebuggedProvisioner) Prepare(raws ...interface{}) error { 184 return p.Provisioner.Prepare(raws...) 185 } 186 187 func (p *DebuggedProvisioner) Provision(ui Ui, comm Communicator) error { 188 p.lock.Lock() 189 cancelCh := make(chan struct{}) 190 p.cancelCh = cancelCh 191 192 // Setup the done channel, which is trigger when we're done 193 doneCh := make(chan struct{}) 194 defer close(doneCh) 195 p.doneCh = doneCh 196 p.lock.Unlock() 197 198 defer func() { 199 p.lock.Lock() 200 defer p.lock.Unlock() 201 if p.cancelCh == cancelCh { 202 p.cancelCh = nil 203 } 204 if p.doneCh == doneCh { 205 p.doneCh = nil 206 } 207 }() 208 209 // Use a select to determine if we get cancelled during the wait 210 message := "Pausing before the next provisioner . Press enter to continue." 211 212 result := make(chan string, 1) 213 go func() { 214 line, err := ui.Ask(message) 215 if err != nil { 216 log.Printf("Error asking for input: %s", err) 217 } 218 219 result <- line 220 }() 221 222 select { 223 case <-result: 224 case <-cancelCh: 225 return nil 226 } 227 228 provDoneCh := make(chan error, 1) 229 go p.provision(provDoneCh, ui, comm) 230 231 select { 232 case err := <-provDoneCh: 233 return err 234 case <-cancelCh: 235 p.Provisioner.Cancel() 236 return <-provDoneCh 237 } 238 } 239 240 func (p *DebuggedProvisioner) Cancel() { 241 var doneCh chan struct{} 242 243 p.lock.Lock() 244 if p.cancelCh != nil { 245 close(p.cancelCh) 246 p.cancelCh = nil 247 } 248 if p.doneCh != nil { 249 doneCh = p.doneCh 250 } 251 p.lock.Unlock() 252 253 <-doneCh 254 } 255 256 func (p *DebuggedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) { 257 result <- p.Provisioner.Provision(ui, comm) 258 }