Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
packer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kristopher Ruzic
packer
Commits
6588fe62
Commit
6588fe62
authored
Dec 20, 2013
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #739 from mitchellh/f-prov-pause-before
Provisioner "pause_before" meta-parameter
parents
5eb16895
a2671dc3
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
323 additions
and
7 deletions
+323
-7
packer/provisioner.go
packer/provisioner.go
+79
-0
packer/provisioner_mock.go
packer/provisioner_mock.go
+7
-5
packer/provisioner_test.go
packer/provisioner_test.go
+91
-0
packer/template.go
packer/template.go
+26
-2
packer/template_test.go
packer/template_test.go
+97
-0
website/source/docs/templates/provisioners.html.markdown
website/source/docs/templates/provisioners.html.markdown
+23
-0
No files found.
packer/provisioner.go
View file @
6588fe62
package
packer
import
(
"fmt"
"sync"
"time"
)
// A provisioner is responsible for installing and configuring software
...
...
@@ -65,3 +67,80 @@ func (h *ProvisionHook) 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
)
}
packer/provisioner_mock.go
View file @
6588fe62
...
...
@@ -5,11 +5,12 @@ package packer
type
MockProvisioner
struct
{
ProvFunc
func
()
error
PrepCalled
bool
PrepConfigs
[]
interface
{}
ProvCalled
bool
ProvUi
Ui
CancelCalled
bool
PrepCalled
bool
PrepConfigs
[]
interface
{}
ProvCalled
bool
ProvCommunicator
Communicator
ProvUi
Ui
CancelCalled
bool
}
func
(
t
*
MockProvisioner
)
Prepare
(
configs
...
interface
{})
error
{
...
...
@@ -20,6 +21,7 @@ func (t *MockProvisioner) Prepare(configs ...interface{}) error {
func
(
t
*
MockProvisioner
)
Provision
(
ui
Ui
,
comm
Communicator
)
error
{
t
.
ProvCalled
=
true
t
.
ProvCommunicator
=
comm
t
.
ProvUi
=
ui
if
t
.
ProvFunc
==
nil
{
...
...
packer/provisioner_test.go
View file @
6588fe62
...
...
@@ -80,3 +80,94 @@ func TestProvisionHook_cancel(t *testing.T) {
}
// 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"
)
}
}
packer/template.go
View file @
6588fe62
...
...
@@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"sort"
"time"
)
// The rawTemplate struct represents the structure of a template read
...
...
@@ -63,10 +64,13 @@ type RawPostProcessorConfig struct {
type
RawProvisionerConfig
struct
{
TemplateOnlyExcept
`mapstructure:",squash"`
Type
string
Override
map
[
string
]
interface
{}
Type
string
Override
map
[
string
]
interface
{}
RawPauseBefore
string
`mapstructure:"pause_before"`
RawConfig
interface
{}
pauseBefore
time
.
Duration
}
// 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
}
...
...
@@ -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
}
provisioners
=
append
(
provisioners
,
coreProv
)
}
...
...
packer/template_test.go
View file @
6588fe62
...
...
@@ -6,6 +6,7 @@ import (
"reflect"
"sort"
"testing"
"time"
)
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
)
{
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
)
{
data
:=
`
{
...
...
website/source/docs/templates/provisioners.html.markdown
View file @
6588fe62
...
...
@@ -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
contains the provisioner configuration as normal. This configuration is merged
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.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment