diff --git a/packer/core.go b/packer/core.go index a76951e33..0b3670371 100644 --- a/packer/core.go +++ b/packer/core.go @@ -168,6 +168,11 @@ func (c *Core) Build(n string) (Build, error) { PauseBefore: rawP.PauseBefore, Provisioner: provisioner, } + } else if rawP.Timeout > 0 { + provisioner = &TimeoutProvisioner{ + Timeout: rawP.Timeout, + Provisioner: provisioner, + } } provisioners = append(provisioners, coreBuildProvisioner{ diff --git a/packer/provisioner.go b/packer/provisioner.go index 990313f38..ec350fd50 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -53,8 +53,6 @@ func (h *ProvisionHook) Run(ctx context.Context, name string, ui Ui, comm Commun for _, p := range h.Provisioners { ts := CheckpointReporter.AddSpan(p.TypeName, "provisioner", p.Config) - ctx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() err := p.Provisioner.Provision(ctx, ui, comm) diff --git a/packer/provisioner_timeout.go b/packer/provisioner_timeout.go new file mode 100644 index 000000000..a2af71e7a --- /dev/null +++ b/packer/provisioner_timeout.go @@ -0,0 +1,23 @@ +package packer + +import ( + "context" + "fmt" + "time" +) + +// TimeoutProvisioner is a Provisioner implementation that can timeout after a +// duration +type TimeoutProvisioner struct { + Provisioner + Timeout time.Duration +} + +func (p *TimeoutProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator) error { + ctx, cancel := context.WithTimeout(ctx, p.Timeout) + defer cancel() + + // Use a select to determine if we get cancelled during the wait + ui.Say(fmt.Sprintf("Setting a %s timeout for the next provisioner...", p.Timeout)) + return p.Provisioner.Provision(ctx, ui, comm) +} diff --git a/template/parse.go b/template/parse.go index 0f0aad944..82bf6731f 100644 --- a/template/parse.go +++ b/template/parse.go @@ -233,6 +233,7 @@ func (r *rawTemplate) Template() (*Template, error) { delete(p.Config, "override") delete(p.Config, "pause_before") delete(p.Config, "type") + delete(p.Config, "timeout") if len(p.Config) == 0 { p.Config = nil diff --git a/template/parse_test.go b/template/parse_test.go index d67f31ec3..71d152af6 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -106,6 +106,19 @@ func TestParse(t *testing.T) { false, }, + { + "parse-provisioner-timeout.json", + &Template{ + Provisioners: []*Provisioner{ + { + Type: "something", + Timeout: 5 * time.Minute, + }, + }, + }, + false, + }, + { "parse-provisioner-only.json", &Template{ diff --git a/template/template.go b/template/template.go index 98e8a7eaf..43d7de078 100644 --- a/template/template.go +++ b/template/template.go @@ -148,6 +148,7 @@ type Provisioner struct { Config map[string]interface{} `json:"config,omitempty"` Override map[string]interface{} `json:"override,omitempty"` PauseBefore time.Duration `mapstructure:"pause_before" json:"pause_before,omitempty"` + Timeout time.Duration `mapstructure:"timeout" json:"timeout,omitempty"` } // MarshalJSON conducts the necessary flattening of the Provisioner struct diff --git a/template/template_test.go b/template/template_test.go index 6fa39ab88..7dc5b87d6 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -18,6 +18,11 @@ func TestTemplateValidate(t *testing.T) { File string Err bool }{ + { + "validate-good-prov-timeout.json", + false, + }, + { "validate-no-builders.json", true, diff --git a/template/test-fixtures/parse-provisioner-timeout.json b/template/test-fixtures/parse-provisioner-timeout.json new file mode 100644 index 000000000..ed9853595 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-timeout.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "timeout": "5m" + } + ] +} diff --git a/template/test-fixtures/validate-good-prov-timeout.json b/template/test-fixtures/validate-good-prov-timeout.json new file mode 100644 index 000000000..6343debd4 --- /dev/null +++ b/template/test-fixtures/validate-good-prov-timeout.json @@ -0,0 +1,11 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "timeout": "5m", + "type": "bar", + "only": ["foo"] + }] +} diff --git a/website/source/docs/provisioners/shell.html.md.erb b/website/source/docs/provisioners/shell.html.md.erb index d4b40c96d..781c7e88d 100644 --- a/website/source/docs/provisioners/shell.html.md.erb +++ b/website/source/docs/provisioners/shell.html.md.erb @@ -180,6 +180,7 @@ executing the next script: "type": "shell", "script": "script.sh", "pause_before": "10s" + "timeout": "10s" } ``` diff --git a/website/source/docs/templates/provisioners.html.md b/website/source/docs/templates/provisioners.html.md index b74929562..8cb4bc2f9 100644 --- a/website/source/docs/templates/provisioners.html.md +++ b/website/source/docs/templates/provisioners.html.md @@ -140,3 +140,25 @@ that provisioner. By default, there is no pause. An example is shown below: For the above provisioner, Packer will wait 10 seconds before uploading and executing the shell script. + +## Timeout + +Sometimes a command can take much more time than expected + +Every provisioner definition in a Packer template can take a special +configuration `timeout` that is the amount of time to wait before +considering that the provisioner failed. By default, there is no timeout. An +example is shown below: + +``` json +{ + "type": "shell", + "script": "script.sh", + "timeout": "5m" +} +``` + +For the above provisioner, Packer will cancel the script if it takes more than +5 minutes. + +Timeout has no effect in debug mode.