Add RetriedProvisioner to allow retry provisioners (#9061)
This commit is contained in:
parent
d580ea7950
commit
553b1fb9f8
|
@ -51,6 +51,7 @@ commands:
|
|||
jobs:
|
||||
test-linux:
|
||||
executor: golang
|
||||
resource_class: large
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- checkout
|
||||
|
|
|
@ -207,6 +207,23 @@ var (
|
|||
},
|
||||
}
|
||||
|
||||
emptyMockBuilder = &MockBuilder{
|
||||
Config: MockConfig{
|
||||
NestedMockConfig: NestedMockConfig{
|
||||
Tags: []MockTag{},
|
||||
},
|
||||
Nested: NestedMockConfig{},
|
||||
NestedSlice: []NestedMockConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
emptyMockProvisioner = &MockProvisioner{
|
||||
Config: MockConfig{
|
||||
NestedMockConfig: NestedMockConfig{Tags: []MockTag{}},
|
||||
NestedSlice: []NestedMockConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
dynamicTagList = []MockTag{
|
||||
{
|
||||
Key: "first_tag_key",
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
// starts resources to provision them.
|
||||
build {
|
||||
sources = [
|
||||
"source.virtualbox-iso.ubuntu-1204"
|
||||
]
|
||||
|
||||
provisioner "shell" {
|
||||
pause_before = "10s"
|
||||
max_retries = 5
|
||||
}
|
||||
}
|
||||
|
||||
source "virtualbox-iso" "ubuntu-1204" {
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
// starts resources to provision them.
|
||||
build {
|
||||
sources = [
|
||||
"source.virtualbox-iso.ubuntu-1204"
|
||||
]
|
||||
|
||||
provisioner "shell" {
|
||||
timeout = "10s"
|
||||
}
|
||||
}
|
||||
|
||||
source "virtualbox-iso" "ubuntu-1204" {
|
||||
}
|
|
@ -2,6 +2,7 @@ package hcl2template
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
|
@ -10,8 +11,11 @@ import (
|
|||
|
||||
// ProvisionerBlock references a detected but unparsed provisioner
|
||||
type ProvisionerBlock struct {
|
||||
PType string
|
||||
PName string
|
||||
PType string
|
||||
PName string
|
||||
PauseBefore time.Duration
|
||||
MaxRetries int
|
||||
Timeout time.Duration
|
||||
HCL2Ref
|
||||
}
|
||||
|
||||
|
@ -21,17 +25,44 @@ func (p *ProvisionerBlock) String() string {
|
|||
|
||||
func (p *Parser) decodeProvisioner(block *hcl.Block) (*ProvisionerBlock, hcl.Diagnostics) {
|
||||
var b struct {
|
||||
Name string `hcl:"name,optional"`
|
||||
Rest hcl.Body `hcl:",remain"`
|
||||
Name string `hcl:"name,optional"`
|
||||
PauseBefore string `hcl:"pause_before,optional"`
|
||||
MaxRetries int `hcl:"max_retries,optional"`
|
||||
Timeout string `hcl:"timeout,optional"`
|
||||
Rest hcl.Body `hcl:",remain"`
|
||||
}
|
||||
diags := gohcl.DecodeBody(block.Body, nil, &b)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
provisioner := &ProvisionerBlock{
|
||||
PType: block.Labels[0],
|
||||
PName: b.Name,
|
||||
HCL2Ref: newHCL2Ref(block, b.Rest),
|
||||
PType: block.Labels[0],
|
||||
PName: b.Name,
|
||||
MaxRetries: b.MaxRetries,
|
||||
HCL2Ref: newHCL2Ref(block, b.Rest),
|
||||
}
|
||||
|
||||
if b.PauseBefore != "" {
|
||||
pauseBefore, err := time.ParseDuration(b.PauseBefore)
|
||||
if err != nil {
|
||||
return nil, append(diags, &hcl.Diagnostic{
|
||||
Summary: "Failed to parse pause_before duration",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
provisioner.PauseBefore = pauseBefore
|
||||
}
|
||||
|
||||
if b.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(b.Timeout)
|
||||
if err != nil {
|
||||
return nil, append(diags, &hcl.Diagnostic{
|
||||
Summary: "Failed to parse timeout duration",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
provisioner.Timeout = timeout
|
||||
}
|
||||
|
||||
if !p.ProvisionersSchemas.Has(provisioner.PType) {
|
||||
|
|
|
@ -195,6 +195,26 @@ func (p *Parser) getCoreBuildProvisioners(source *SourceBlock, blocks []*Provisi
|
|||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we're pausing, we wrap the provisioner in a special pauser.
|
||||
if pb.PauseBefore != 0 {
|
||||
provisioner = &packer.PausedProvisioner{
|
||||
PauseBefore: pb.PauseBefore,
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
} else if pb.Timeout != 0 {
|
||||
provisioner = &packer.TimeoutProvisioner{
|
||||
Timeout: pb.Timeout,
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
}
|
||||
if pb.MaxRetries != 0 {
|
||||
provisioner = &packer.RetriedProvisioner{
|
||||
MaxRetries: pb.MaxRetries,
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, packer.CoreBuildProvisioner{
|
||||
PType: pb.PType,
|
||||
PName: pb.PName,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
@ -173,6 +175,90 @@ func TestParser_complete(t *testing.T) {
|
|||
parseWantDiags: true,
|
||||
parseWantDiagHasErrors: true,
|
||||
},
|
||||
{"provisioner with wrappers pause_before and max_retriers",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/provisioner_paused_before_retry.pkr.hcl", nil, nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Sources: map[SourceRef]*SourceBlock{
|
||||
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
|
||||
},
|
||||
Builds: Builds{
|
||||
&BuildBlock{
|
||||
Sources: []SourceRef{refVBIsoUbuntu1204},
|
||||
ProvisionerBlocks: []*ProvisionerBlock{
|
||||
{
|
||||
PType: "shell",
|
||||
PauseBefore: time.Second * 10,
|
||||
MaxRetries: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false, false,
|
||||
[]packer.Build{
|
||||
&packer.CoreBuild{
|
||||
Type: "virtualbox-iso",
|
||||
Prepared: true,
|
||||
Builder: emptyMockBuilder,
|
||||
Provisioners: []packer.CoreBuildProvisioner{
|
||||
{
|
||||
PType: "shell",
|
||||
Provisioner: &packer.RetriedProvisioner{
|
||||
MaxRetries: 5,
|
||||
Provisioner: &packer.PausedProvisioner{
|
||||
PauseBefore: time.Second * 10,
|
||||
Provisioner: emptyMockProvisioner,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PostProcessors: [][]packer.CoreBuildPostProcessor{},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"provisioner with wrappers timeout",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/provisioner_timeout.pkr.hcl", nil, nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Sources: map[SourceRef]*SourceBlock{
|
||||
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
|
||||
},
|
||||
Builds: Builds{
|
||||
&BuildBlock{
|
||||
Sources: []SourceRef{refVBIsoUbuntu1204},
|
||||
ProvisionerBlocks: []*ProvisionerBlock{
|
||||
{
|
||||
PType: "shell",
|
||||
Timeout: time.Second * 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false, false,
|
||||
[]packer.Build{
|
||||
&packer.CoreBuild{
|
||||
Type: "virtualbox-iso",
|
||||
Prepared: true,
|
||||
Builder: emptyMockBuilder,
|
||||
Provisioners: []packer.CoreBuildProvisioner{
|
||||
{
|
||||
PType: "shell",
|
||||
Provisioner: &packer.TimeoutProvisioner{
|
||||
Timeout: time.Second * 10,
|
||||
Provisioner: emptyMockProvisioner,
|
||||
},
|
||||
},
|
||||
},
|
||||
PostProcessors: [][]packer.CoreBuildPostProcessor{},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
testParse(t, tests)
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@ type FlatMockProvisioner struct {
|
|||
PrepCalled *bool `cty:"prep_called"`
|
||||
PrepConfigs []interface{} `cty:"prep_configs"`
|
||||
ProvCalled *bool `cty:"prov_called"`
|
||||
ProvRetried *bool `cty:"prov_retried"`
|
||||
ProvCommunicator Communicator `cty:"prov_communicator"`
|
||||
ProvUi Ui `cty:"prov_ui"`
|
||||
}
|
||||
|
@ -174,6 +175,7 @@ func (*FlatMockProvisioner) HCL2Spec() map[string]hcldec.Spec {
|
|||
"prep_called": &hcldec.AttrSpec{Name: "prep_called", Type: cty.Bool, Required: false},
|
||||
"prep_configs": &hcldec.AttrSpec{Name: "prep_configs", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */
|
||||
"prov_called": &hcldec.AttrSpec{Name: "prov_called", Type: cty.Bool, Required: false},
|
||||
"prov_retried": &hcldec.AttrSpec{Name: "prov_retried", Type: cty.Bool, Required: false},
|
||||
"prov_communicator": &hcldec.AttrSpec{Name: "prov_communicator", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */
|
||||
"prov_ui": &hcldec.AttrSpec{Name: "prov_ui", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */
|
||||
}
|
||||
|
|
|
@ -170,6 +170,12 @@ func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName
|
|||
Provisioner: provisioner,
|
||||
}
|
||||
}
|
||||
if rawP.MaxRetries != 0 {
|
||||
provisioner = &RetriedProvisioner{
|
||||
MaxRetries: rawP.MaxRetries,
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
}
|
||||
cbp = CoreBuildProvisioner{
|
||||
PType: rawP.Type,
|
||||
Provisioner: provisioner,
|
||||
|
|
|
@ -2,6 +2,7 @@ package packer
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
@ -786,3 +787,42 @@ func testCoreTemplate(t *testing.T, c *CoreConfig, p string) {
|
|||
|
||||
c.Template = tpl
|
||||
}
|
||||
|
||||
func TestCoreBuild_provRetry(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-prov-retry.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
p := TestProvisioner(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ui := testUi()
|
||||
p.ProvFunc = func(ctx context.Context) error {
|
||||
return errors.New("failed")
|
||||
}
|
||||
|
||||
artifact, err := build.Run(context.Background(), ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
if !p.ProvRetried {
|
||||
t.Fatal("provisioner should retry")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,6 +162,48 @@ func (p *PausedProvisioner) Provision(ctx context.Context, ui Ui, comm Communica
|
|||
return p.Provisioner.Provision(ctx, ui, comm, generatedData)
|
||||
}
|
||||
|
||||
// RetriedProvisioner is a Provisioner implementation that retries
|
||||
// the provisioner whenever there's an error.
|
||||
type RetriedProvisioner struct {
|
||||
MaxRetries int
|
||||
Provisioner Provisioner
|
||||
}
|
||||
|
||||
func (r *RetriedProvisioner) ConfigSpec() hcldec.ObjectSpec { return r.ConfigSpec() }
|
||||
func (r *RetriedProvisioner) FlatConfig() interface{} { return r.FlatConfig() }
|
||||
func (r *RetriedProvisioner) Prepare(raws ...interface{}) error {
|
||||
return r.Provisioner.Prepare(raws...)
|
||||
}
|
||||
|
||||
func (r *RetriedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error {
|
||||
if ctx.Err() != nil { // context was cancelled
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
err := r.Provisioner.Provision(ctx, ui, comm, generatedData)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
leftTries := r.MaxRetries
|
||||
for ; leftTries > 0; leftTries-- {
|
||||
if ctx.Err() != nil { // context was cancelled
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Provisioner failed with %q, retrying with %d trie(s) left", err, leftTries))
|
||||
|
||||
err := r.Provisioner.Provision(ctx, ui, comm, generatedData)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
ui.Say("retry limit reached.")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DebuggedProvisioner is a Provisioner implementation that waits until a key
|
||||
// press before the provisioner is actually run.
|
||||
type DebuggedProvisioner struct {
|
||||
|
|
|
@ -14,6 +14,7 @@ type MockProvisioner struct {
|
|||
PrepCalled bool
|
||||
PrepConfigs []interface{}
|
||||
ProvCalled bool
|
||||
ProvRetried bool
|
||||
ProvCommunicator Communicator
|
||||
ProvUi Ui
|
||||
}
|
||||
|
@ -29,6 +30,11 @@ func (t *MockProvisioner) Prepare(configs ...interface{}) error {
|
|||
}
|
||||
|
||||
func (t *MockProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error {
|
||||
if t.ProvCalled {
|
||||
t.ProvRetried = true
|
||||
return nil
|
||||
}
|
||||
|
||||
t.ProvCalled = true
|
||||
t.ProvCommunicator = comm
|
||||
t.ProvUi = ui
|
||||
|
|
|
@ -2,6 +2,7 @@ package packer
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -229,3 +230,114 @@ func TestDebuggedProvisionerCancel(t *testing.T) {
|
|||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetriedProvisioner_impl(t *testing.T) {
|
||||
var _ Provisioner = new(RetriedProvisioner)
|
||||
}
|
||||
|
||||
func TestRetriedProvisionerPrepare(t *testing.T) {
|
||||
mock := new(MockProvisioner)
|
||||
prov := &RetriedProvisioner{
|
||||
Provisioner: mock,
|
||||
}
|
||||
|
||||
err := prov.Prepare(42)
|
||||
if err != nil {
|
||||
t.Fatal("should not have errored")
|
||||
}
|
||||
if !mock.PrepCalled {
|
||||
t.Fatal("prepare should be called")
|
||||
}
|
||||
if mock.PrepConfigs[0] != 42 {
|
||||
t.Fatal("should have proper configs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetriedProvisionerProvision(t *testing.T) {
|
||||
mock := &MockProvisioner{
|
||||
ProvFunc: func(ctx context.Context) error {
|
||||
return errors.New("failed")
|
||||
},
|
||||
}
|
||||
|
||||
prov := &RetriedProvisioner{
|
||||
MaxRetries: 2,
|
||||
Provisioner: mock,
|
||||
}
|
||||
|
||||
ui := testUi()
|
||||
comm := new(MockCommunicator)
|
||||
err := prov.Provision(context.Background(), ui, comm, make(map[string]interface{}))
|
||||
if err != nil {
|
||||
t.Fatal("should not have errored")
|
||||
}
|
||||
if !mock.ProvCalled {
|
||||
t.Fatal("prov should be called")
|
||||
}
|
||||
if !mock.ProvRetried {
|
||||
t.Fatal("prov should be retried")
|
||||
}
|
||||
if mock.ProvUi != ui {
|
||||
t.Fatal("should have proper ui")
|
||||
}
|
||||
if mock.ProvCommunicator != comm {
|
||||
t.Fatal("should have proper comm")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetriedProvisionerCancelledProvision(t *testing.T) {
|
||||
// Don't retry if context is cancelled
|
||||
ctx, topCtxCancel := context.WithCancel(context.Background())
|
||||
|
||||
mock := &MockProvisioner{
|
||||
ProvFunc: func(ctx context.Context) error {
|
||||
topCtxCancel()
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
},
|
||||
}
|
||||
|
||||
prov := &RetriedProvisioner{
|
||||
MaxRetries: 2,
|
||||
Provisioner: mock,
|
||||
}
|
||||
|
||||
ui := testUi()
|
||||
comm := new(MockCommunicator)
|
||||
err := prov.Provision(ctx, ui, comm, make(map[string]interface{}))
|
||||
if err == nil {
|
||||
t.Fatal("should have errored")
|
||||
}
|
||||
if !mock.ProvCalled {
|
||||
t.Fatal("prov should be called")
|
||||
}
|
||||
if mock.ProvRetried {
|
||||
t.Fatal("prov should NOT be retried")
|
||||
}
|
||||
if mock.ProvUi != ui {
|
||||
t.Fatal("should have proper ui")
|
||||
}
|
||||
if mock.ProvCommunicator != comm {
|
||||
t.Fatal("should have proper comm")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetriedProvisionerCancel(t *testing.T) {
|
||||
topCtx, cancelTopCtx := context.WithCancel(context.Background())
|
||||
|
||||
mock := new(MockProvisioner)
|
||||
prov := &RetriedProvisioner{
|
||||
Provisioner: mock,
|
||||
}
|
||||
|
||||
mock.ProvFunc = func(ctx context.Context) error {
|
||||
cancelTopCtx()
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
err := prov.Provision(topCtx, testUi(), new(MockCommunicator), make(map[string]interface{}))
|
||||
if err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "test",
|
||||
"max_retries": 1
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package shell_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/tests/acc"
|
||||
"github.com/hashicorp/packer/provisioner/shell"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
||||
"github.com/hashicorp/packer/command"
|
||||
)
|
||||
|
||||
func TestShellLocalProvisionerWithRetryOption(t *testing.T) {
|
||||
acc.TestProvisionersPreCheck("shell-local", t)
|
||||
acc.TestProvisionersAgainstBuilders(new(ShellLocalProvisionerAccTest), t)
|
||||
}
|
||||
|
||||
type ShellLocalProvisionerAccTest struct{}
|
||||
|
||||
func (s *ShellLocalProvisionerAccTest) GetName() string {
|
||||
return "file"
|
||||
}
|
||||
|
||||
func (s *ShellLocalProvisionerAccTest) GetConfig() (string, error) {
|
||||
filePath := filepath.Join("./test-fixtures", "shell-local-provisioner.txt")
|
||||
config, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Expected to find %s", filePath)
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
file, err := ioutil.ReadAll(config)
|
||||
return string(file), err
|
||||
}
|
||||
|
||||
func (s *ShellLocalProvisionerAccTest) GetProvisionerStore() packer.MapOfProvisioner {
|
||||
return packer.MapOfProvisioner{
|
||||
"shell-local": func() (packer.Provisioner, error) { return &shell.Provisioner{}, nil },
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ShellLocalProvisionerAccTest) IsCompatible(builder string, vmOS string) bool {
|
||||
return vmOS == "linux"
|
||||
}
|
||||
|
||||
func (s *ShellLocalProvisionerAccTest) RunTest(c *command.BuildCommand, args []string) error {
|
||||
if code := c.Run(args); code != 0 {
|
||||
ui := c.Meta.Ui.(*packer.BasicUi)
|
||||
out := ui.Writer.(*bytes.Buffer)
|
||||
err := ui.ErrorWriter.(*bytes.Buffer)
|
||||
return fmt.Errorf(
|
||||
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
|
||||
out.String(),
|
||||
err.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ ! -f file.txt ]] ; then
|
||||
echo 'hello' > file.txt
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shell-local",
|
||||
"script": "test-fixtures/script.sh",
|
||||
"max_retries" : 5
|
||||
}
|
|
@ -76,6 +76,7 @@ func (r *rawTemplate) decodeProvisioner(raw interface{}) (Provisioner, error) {
|
|||
delete(p.Config, "only")
|
||||
delete(p.Config, "override")
|
||||
delete(p.Config, "pause_before")
|
||||
delete(p.Config, "max_retries")
|
||||
delete(p.Config, "type")
|
||||
delete(p.Config, "timeout")
|
||||
|
||||
|
|
|
@ -106,6 +106,19 @@ func TestParse(t *testing.T) {
|
|||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-retry.json",
|
||||
&Template{
|
||||
Provisioners: []*Provisioner{
|
||||
{
|
||||
Type: "something",
|
||||
MaxRetries: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-timeout.json",
|
||||
&Template{
|
||||
|
|
|
@ -141,6 +141,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"`
|
||||
MaxRetries int `mapstructure:"max_retries" json:"max_retries,omitempty"`
|
||||
Timeout time.Duration `mapstructure:"timeout" json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ type FlatProvisioner struct {
|
|||
Config map[string]interface{} `json:"config,omitempty" cty:"config"`
|
||||
Override map[string]interface{} `json:"override,omitempty" cty:"override"`
|
||||
PauseBefore *string `mapstructure:"pause_before" json:"pause_before,omitempty" cty:"pause_before"`
|
||||
MaxRetries *int `mapstructure:"max_retries" json:"max_retries,omitempty" cty:"max_retries"`
|
||||
Timeout *string `mapstructure:"timeout" json:"timeout,omitempty" cty:"timeout"`
|
||||
}
|
||||
|
||||
|
@ -36,6 +37,7 @@ func (*FlatProvisioner) HCL2Spec() map[string]hcldec.Spec {
|
|||
"config": &hcldec.AttrSpec{Name: "config", Type: cty.Map(cty.String), Required: false},
|
||||
"override": &hcldec.AttrSpec{Name: "override", Type: cty.Map(cty.String), Required: false},
|
||||
"pause_before": &hcldec.AttrSpec{Name: "pause_before", Type: cty.String, Required: false},
|
||||
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
|
||||
"timeout": &hcldec.AttrSpec{Name: "timeout", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "something",
|
||||
"max_retries": 5
|
||||
}
|
||||
]
|
||||
}
|
|
@ -177,6 +177,27 @@ 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.
|
||||
|
||||
## Retry on error
|
||||
|
||||
With certain provisioners it is sometimes desirable to retry when it fails.
|
||||
Specifically, in cases where the provisioner depends on external processes that are not done yet.
|
||||
|
||||
|
||||
Every provisioner definition in a Packer template can take a special
|
||||
configuration `max_retries` that is the maximum number of times a provisioner will retry on error.
|
||||
By default, there `max_retries` is zero and there is no retry on error. An example is shown below:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shell",
|
||||
"script": "script.sh",
|
||||
"max_retries": 5
|
||||
}
|
||||
```
|
||||
|
||||
For the above provisioner, Packer will retry maximum five times until stops failing.
|
||||
If after five retries the provisioner still fails, then the complete build will fail.
|
||||
|
||||
## Timeout
|
||||
|
||||
Sometimes a command can take much more time than expected
|
||||
|
|
|
@ -2,6 +2,8 @@ Parameters common to all provisioners:
|
|||
|
||||
- `pause_before` (duration) - Sleep for duration before execution.
|
||||
|
||||
- `max_retries` (int) - Max times the provisioner will retry in case of failure. Defaults to zero (0). Zero means an error will not be retried.
|
||||
|
||||
- `only` (array of string) - Only run the provisioner for listed builder(s)
|
||||
by name.
|
||||
|
||||
|
|
Loading…
Reference in New Issue