Merge pull request #739 from mitchellh/f-prov-pause-before
Provisioner "pause_before" meta-parameter
This commit is contained in:
commit
6588fe627d
|
@ -1,7 +1,9 @@
|
||||||
package packer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A provisioner is responsible for installing and configuring software
|
// A provisioner is responsible for installing and configuring software
|
||||||
|
@ -65,3 +67,80 @@ func (h *ProvisionHook) Cancel() {
|
||||||
h.runningProvisioner.Cancel()
|
h.runningProvisioner.Cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PausedProvisioner is a Provisioner implementation that pauses before
|
||||||
|
// the provisioner is actually run.
|
||||||
|
type PausedProvisioner struct {
|
||||||
|
PauseBefore time.Duration
|
||||||
|
Provisioner Provisioner
|
||||||
|
|
||||||
|
cancelCh chan struct{}
|
||||||
|
doneCh chan struct{}
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PausedProvisioner) Prepare(raws ...interface{}) error {
|
||||||
|
return p.Provisioner.Prepare(raws...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PausedProvisioner) Provision(ui Ui, comm Communicator) error {
|
||||||
|
p.lock.Lock()
|
||||||
|
cancelCh := make(chan struct{})
|
||||||
|
p.cancelCh = cancelCh
|
||||||
|
|
||||||
|
// Setup the done channel, which is trigger when we're done
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
defer close(doneCh)
|
||||||
|
p.doneCh = doneCh
|
||||||
|
p.lock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
if p.cancelCh == cancelCh {
|
||||||
|
p.cancelCh = nil
|
||||||
|
}
|
||||||
|
if p.doneCh == doneCh {
|
||||||
|
p.doneCh = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Use a select to determine if we get cancelled during the wait
|
||||||
|
ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore))
|
||||||
|
select {
|
||||||
|
case <-time.After(p.PauseBefore):
|
||||||
|
case <-cancelCh:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
provDoneCh := make(chan error, 1)
|
||||||
|
go p.provision(provDoneCh, ui, comm)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-provDoneCh:
|
||||||
|
return err
|
||||||
|
case <-cancelCh:
|
||||||
|
p.Provisioner.Cancel()
|
||||||
|
return <-provDoneCh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PausedProvisioner) Cancel() {
|
||||||
|
var doneCh chan struct{}
|
||||||
|
|
||||||
|
p.lock.Lock()
|
||||||
|
if p.cancelCh != nil {
|
||||||
|
close(p.cancelCh)
|
||||||
|
p.cancelCh = nil
|
||||||
|
}
|
||||||
|
if p.doneCh != nil {
|
||||||
|
doneCh = p.doneCh
|
||||||
|
}
|
||||||
|
p.lock.Unlock()
|
||||||
|
|
||||||
|
<-doneCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PausedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
|
||||||
|
result <- p.Provisioner.Provision(ui, comm)
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ type MockProvisioner struct {
|
||||||
PrepCalled bool
|
PrepCalled bool
|
||||||
PrepConfigs []interface{}
|
PrepConfigs []interface{}
|
||||||
ProvCalled bool
|
ProvCalled bool
|
||||||
|
ProvCommunicator Communicator
|
||||||
ProvUi Ui
|
ProvUi Ui
|
||||||
CancelCalled bool
|
CancelCalled bool
|
||||||
}
|
}
|
||||||
|
@ -20,6 +21,7 @@ func (t *MockProvisioner) Prepare(configs ...interface{}) error {
|
||||||
|
|
||||||
func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
|
func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
|
||||||
t.ProvCalled = true
|
t.ProvCalled = true
|
||||||
|
t.ProvCommunicator = comm
|
||||||
t.ProvUi = ui
|
t.ProvUi = ui
|
||||||
|
|
||||||
if t.ProvFunc == nil {
|
if t.ProvFunc == nil {
|
||||||
|
|
|
@ -80,3 +80,94 @@ func TestProvisionHook_cancel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mitchellh): Test that they're run in the proper order
|
// TODO(mitchellh): Test that they're run in the proper order
|
||||||
|
|
||||||
|
func TestPausedProvisioner_impl(t *testing.T) {
|
||||||
|
var _ Provisioner = new(PausedProvisioner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPausedProvisionerPrepare(t *testing.T) {
|
||||||
|
mock := new(MockProvisioner)
|
||||||
|
prov := &PausedProvisioner{
|
||||||
|
Provisioner: mock,
|
||||||
|
}
|
||||||
|
|
||||||
|
prov.Prepare(42)
|
||||||
|
if !mock.PrepCalled {
|
||||||
|
t.Fatal("prepare should be called")
|
||||||
|
}
|
||||||
|
if mock.PrepConfigs[0] != 42 {
|
||||||
|
t.Fatal("should have proper configs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPausedProvisionerProvision(t *testing.T) {
|
||||||
|
mock := new(MockProvisioner)
|
||||||
|
prov := &PausedProvisioner{
|
||||||
|
Provisioner: mock,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui := testUi()
|
||||||
|
comm := new(MockCommunicator)
|
||||||
|
prov.Provision(ui, comm)
|
||||||
|
if !mock.ProvCalled {
|
||||||
|
t.Fatal("prov should be called")
|
||||||
|
}
|
||||||
|
if mock.ProvUi != ui {
|
||||||
|
t.Fatal("should have proper ui")
|
||||||
|
}
|
||||||
|
if mock.ProvCommunicator != comm {
|
||||||
|
t.Fatal("should have proper comm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPausedProvisionerProvision_waits(t *testing.T) {
|
||||||
|
mock := new(MockProvisioner)
|
||||||
|
prov := &PausedProvisioner{
|
||||||
|
PauseBefore: 50 * time.Millisecond,
|
||||||
|
Provisioner: mock,
|
||||||
|
}
|
||||||
|
|
||||||
|
dataCh := make(chan struct{})
|
||||||
|
mock.ProvFunc = func() error {
|
||||||
|
close(dataCh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
go prov.Provision(testUi(), new(MockCommunicator))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(10 * time.Millisecond):
|
||||||
|
case <-dataCh:
|
||||||
|
t.Fatal("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
t.Fatal("never called")
|
||||||
|
case <-dataCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPausedProvisionerCancel(t *testing.T) {
|
||||||
|
mock := new(MockProvisioner)
|
||||||
|
prov := &PausedProvisioner{
|
||||||
|
Provisioner: mock,
|
||||||
|
}
|
||||||
|
|
||||||
|
provCh := make(chan struct{})
|
||||||
|
mock.ProvFunc = func() error {
|
||||||
|
close(provCh)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start provisioning and wait for it to start
|
||||||
|
go prov.Provision(testUi(), new(MockCommunicator))
|
||||||
|
<-provCh
|
||||||
|
|
||||||
|
// Cancel it
|
||||||
|
prov.Cancel()
|
||||||
|
if !mock.CancelCalled {
|
||||||
|
t.Fatal("cancel should be called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The rawTemplate struct represents the structure of a template read
|
// The rawTemplate struct represents the structure of a template read
|
||||||
|
@ -65,8 +66,11 @@ type RawProvisionerConfig struct {
|
||||||
|
|
||||||
Type string
|
Type string
|
||||||
Override map[string]interface{}
|
Override map[string]interface{}
|
||||||
|
RawPauseBefore string `mapstructure:"pause_before"`
|
||||||
|
|
||||||
RawConfig interface{}
|
RawConfig interface{}
|
||||||
|
|
||||||
|
pauseBefore time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawVariable represents a variable configuration within a template.
|
// RawVariable represents a variable configuration within a template.
|
||||||
|
@ -289,6 +293,19 @@ func ParseTemplate(data []byte) (t *Template, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup the pause settings
|
||||||
|
if raw.RawPauseBefore != "" {
|
||||||
|
duration, err := time.ParseDuration(raw.RawPauseBefore)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(
|
||||||
|
errors, fmt.Errorf(
|
||||||
|
"provisioner %d: pause_before invalid: %s",
|
||||||
|
i+1, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.pauseBefore = duration
|
||||||
|
}
|
||||||
|
|
||||||
raw.RawConfig = v
|
raw.RawConfig = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,6 +515,13 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rawProvisioner.pauseBefore > 0 {
|
||||||
|
provisioner = &PausedProvisioner{
|
||||||
|
PauseBefore: rawProvisioner.pauseBefore,
|
||||||
|
Provisioner: provisioner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
coreProv := coreBuildProvisioner{provisioner, configs}
|
coreProv := coreBuildProvisioner{provisioner, configs}
|
||||||
provisioners = append(provisioners, coreProv)
|
provisioners = append(provisioners, coreProv)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testTemplateComponentFinder() *ComponentFinder {
|
func testTemplateComponentFinder() *ComponentFinder {
|
||||||
|
@ -445,6 +446,38 @@ func TestParseTemplate_Provisioners(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseTemplate_ProvisionerPauseBefore(t *testing.T) {
|
||||||
|
data := `
|
||||||
|
{
|
||||||
|
"builders": [{"type": "foo"}],
|
||||||
|
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"pause_before": "10s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
result, err := ParseTemplate([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("err: %s", err)
|
||||||
|
}
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("should have result")
|
||||||
|
}
|
||||||
|
if len(result.Provisioners) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", result.Provisioners)
|
||||||
|
}
|
||||||
|
if result.Provisioners[0].Type != "shell" {
|
||||||
|
t.Fatalf("bad: %#v", result.Provisioners[0].Type)
|
||||||
|
}
|
||||||
|
if result.Provisioners[0].pauseBefore != 10*time.Second {
|
||||||
|
t.Fatalf("bad: %s", result.Provisioners[0].pauseBefore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseTemplate_Variables(t *testing.T) {
|
func TestParseTemplate_Variables(t *testing.T) {
|
||||||
data := `
|
data := `
|
||||||
{
|
{
|
||||||
|
@ -1278,6 +1311,70 @@ func TestTemplate_Build_ProvisionerOverrideBad(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateBuild_ProvisionerPauseBefore(t *testing.T) {
|
||||||
|
data := `
|
||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{
|
||||||
|
"name": "test1",
|
||||||
|
"type": "test-builder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "test-prov",
|
||||||
|
"pause_before": "5s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
template, err := ParseTemplate([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := new(MockBuilder)
|
||||||
|
builderMap := map[string]Builder{
|
||||||
|
"test-builder": builder,
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner := &MockProvisioner{}
|
||||||
|
provisionerMap := map[string]Provisioner{
|
||||||
|
"test-prov": provisioner,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderFactory := func(n string) (Builder, error) { return builderMap[n], nil }
|
||||||
|
provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil }
|
||||||
|
components := &ComponentFinder{
|
||||||
|
Builder: builderFactory,
|
||||||
|
Provisioner: provFactory,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the build, verifying we can get it without issue, but also
|
||||||
|
// that the proper builder was looked up and used for the build.
|
||||||
|
build, err := template.Build("test1", components)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
coreBuild, ok := build.(*coreBuild)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("should be okay")
|
||||||
|
}
|
||||||
|
if len(coreBuild.provisioners) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", coreBuild.provisioners)
|
||||||
|
}
|
||||||
|
if pp, ok := coreBuild.provisioners[0].provisioner.(*PausedProvisioner); !ok {
|
||||||
|
t.Fatalf("should be paused provisioner")
|
||||||
|
} else {
|
||||||
|
if pp.PauseBefore != 5*time.Second {
|
||||||
|
t.Fatalf("bad: %#v", pp.PauseBefore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTemplateBuild_variables(t *testing.T) {
|
func TestTemplateBuild_variables(t *testing.T) {
|
||||||
data := `
|
data := `
|
||||||
{
|
{
|
||||||
|
|
|
@ -112,3 +112,26 @@ JSON object where the key is the name of a [builder definition](/docs/templates/
|
||||||
The value of this is in turn another JSON object. This JSON object simply
|
The value of this is in turn another JSON object. This JSON object simply
|
||||||
contains the provisioner configuration as normal. This configuration is merged
|
contains the provisioner configuration as normal. This configuration is merged
|
||||||
into the default provisioner configuration.
|
into the default provisioner configuration.
|
||||||
|
|
||||||
|
## Pausing Before Running
|
||||||
|
|
||||||
|
With certain provisioners it is sometimes desirable to pause for some period
|
||||||
|
of time before running it. Specifically, in cases where a provisioner reboots
|
||||||
|
the machine, you may want to wait for some period of time before starting
|
||||||
|
the next provisioner.
|
||||||
|
|
||||||
|
Every provisioner definition in a Packer template can take a special
|
||||||
|
configuration `pause_before` that is the amount of time to pause before
|
||||||
|
running that provisioner. By default, there is no pause. An example
|
||||||
|
is shown below:
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"script": "script.sh",
|
||||||
|
"pause_before": "10s"
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
For the above provisioner, Packer will wait 10 seconds before uploading
|
||||||
|
and executing the shell script.
|
||||||
|
|
Loading…
Reference in New Issue