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
339a4ccd
Commit
339a4ccd
authored
Jun 14, 2015
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
provisioner/windows-shell
parent
b25b7d1f
Changes
3
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
780 additions
and
0 deletions
+780
-0
plugin/provisioner-windows-shell/main.go
plugin/provisioner-windows-shell/main.go
+15
-0
provisioner/windows-shell/provisioner.go
provisioner/windows-shell/provisioner.go
+324
-0
provisioner/windows-shell/provisioner_test.go
provisioner/windows-shell/provisioner_test.go
+441
-0
No files found.
plugin/provisioner-windows-shell/main.go
0 → 100644
View file @
339a4ccd
package
main
import
(
"github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/packer/provisioner/windows-shell"
)
func
main
()
{
server
,
err
:=
plugin
.
Server
()
if
err
!=
nil
{
panic
(
err
)
}
server
.
RegisterProvisioner
(
new
(
shell
.
Provisioner
))
server
.
Serve
()
}
provisioner/windows-shell/provisioner.go
0 → 100644
View file @
339a4ccd
// This package implements a provisioner for Packer that executes
// shell scripts within the remote machine.
package
shell
import
(
"bufio"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"sort"
"strings"
"time"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
const
DefaultRemotePath
=
"c:/Windows/Temp/script.bat"
var
retryableSleep
=
2
*
time
.
Second
type
Config
struct
{
common
.
PackerConfig
`mapstructure:",squash"`
// If true, the script contains binary and line endings will not be
// converted from Windows to Unix-style.
Binary
bool
// An inline script to execute. Multiple strings are all executed
// in the context of a single shell.
Inline
[]
string
// The local path of the shell script to upload and execute.
Script
string
// An array of multiple scripts to run.
Scripts
[]
string
// An array of environment variables that will be injected before
// your command(s) are executed.
Vars
[]
string
`mapstructure:"environment_vars"`
// The remote path where the local shell script will be uploaded to.
// This should be set to a writable file that is in a pre-existing directory.
RemotePath
string
`mapstructure:"remote_path"`
// The command used to execute the script. The '{{ .Path }}' variable
// should be used to specify where the script goes, {{ .Vars }}
// can be used to inject the environment_vars into the environment.
ExecuteCommand
string
`mapstructure:"execute_command"`
// The timeout for retrying to start the process. Until this timeout
// is reached, if the provisioner can't start a process, it retries.
// This can be set high to allow for reboots.
StartRetryTimeout
time
.
Duration
`mapstructure:"start_retry_timeout"`
// This is used in the template generation to format environment variables
// inside the `ExecuteCommand` template.
EnvVarFormat
string
ctx
interpolate
.
Context
}
type
Provisioner
struct
{
config
Config
}
type
ExecuteCommandTemplate
struct
{
Vars
string
Path
string
}
func
(
p
*
Provisioner
)
Prepare
(
raws
...
interface
{})
error
{
err
:=
config
.
Decode
(
&
p
.
config
,
&
config
.
DecodeOpts
{
Interpolate
:
true
,
InterpolateFilter
:
&
interpolate
.
RenderFilter
{
Exclude
:
[]
string
{
"execute_command"
,
},
},
},
raws
...
)
if
err
!=
nil
{
return
err
}
if
p
.
config
.
EnvVarFormat
==
""
{
p
.
config
.
EnvVarFormat
=
`set "%s=%s" && `
}
if
p
.
config
.
ExecuteCommand
==
""
{
p
.
config
.
ExecuteCommand
=
`{{.Vars}}"{{.Path}}"`
}
if
p
.
config
.
Inline
!=
nil
&&
len
(
p
.
config
.
Inline
)
==
0
{
p
.
config
.
Inline
=
nil
}
if
p
.
config
.
StartRetryTimeout
==
0
{
p
.
config
.
StartRetryTimeout
=
5
*
time
.
Minute
}
if
p
.
config
.
RemotePath
==
""
{
p
.
config
.
RemotePath
=
DefaultRemotePath
}
if
p
.
config
.
Scripts
==
nil
{
p
.
config
.
Scripts
=
make
([]
string
,
0
)
}
if
p
.
config
.
Vars
==
nil
{
p
.
config
.
Vars
=
make
([]
string
,
0
)
}
var
errs
error
if
p
.
config
.
Script
!=
""
&&
len
(
p
.
config
.
Scripts
)
>
0
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"Only one of script or scripts can be specified."
))
}
if
p
.
config
.
Script
!=
""
{
p
.
config
.
Scripts
=
[]
string
{
p
.
config
.
Script
}
}
if
len
(
p
.
config
.
Scripts
)
==
0
&&
p
.
config
.
Inline
==
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"Either a script file or inline script must be specified."
))
}
else
if
len
(
p
.
config
.
Scripts
)
>
0
&&
p
.
config
.
Inline
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"Only a script file or an inline script can be specified, not both."
))
}
for
_
,
path
:=
range
p
.
config
.
Scripts
{
if
_
,
err
:=
os
.
Stat
(
path
);
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Bad script '%s': %s"
,
path
,
err
))
}
}
// Do a check for bad environment variables, such as '=foo', 'foobar'
for
_
,
kv
:=
range
p
.
config
.
Vars
{
vs
:=
strings
.
SplitN
(
kv
,
"="
,
2
)
if
len
(
vs
)
!=
2
||
vs
[
0
]
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Environment variable not in format 'key=value': %s"
,
kv
))
}
}
if
errs
!=
nil
{
return
errs
}
return
nil
}
// This function takes the inline scripts, concatenates them
// into a temporary file and returns a string containing the location
// of said file.
func
extractScript
(
p
*
Provisioner
)
(
string
,
error
)
{
temp
,
err
:=
ioutil
.
TempFile
(
os
.
TempDir
(),
"packer-windows-shell-provisioner"
)
if
err
!=
nil
{
log
.
Printf
(
"Unable to create temporary file for inline scripts: %s"
,
err
)
return
""
,
err
}
writer
:=
bufio
.
NewWriter
(
temp
)
for
_
,
command
:=
range
p
.
config
.
Inline
{
log
.
Printf
(
"Found command: %s"
,
command
)
if
_
,
err
:=
writer
.
WriteString
(
command
+
"
\n
"
);
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"Error preparing shell script: %s"
,
err
)
}
}
if
err
:=
writer
.
Flush
();
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"Error preparing shell script: %s"
,
err
)
}
temp
.
Close
()
return
temp
.
Name
(),
nil
}
func
(
p
*
Provisioner
)
Provision
(
ui
packer
.
Ui
,
comm
packer
.
Communicator
)
error
{
ui
.
Say
(
fmt
.
Sprintf
(
"Provisioning with windows-shell..."
))
scripts
:=
make
([]
string
,
len
(
p
.
config
.
Scripts
))
copy
(
scripts
,
p
.
config
.
Scripts
)
// Build our variables up by adding in the build name and builder type
envVars
:=
make
([]
string
,
len
(
p
.
config
.
Vars
)
+
2
)
envVars
[
0
]
=
"PACKER_BUILD_NAME="
+
p
.
config
.
PackerBuildName
envVars
[
1
]
=
"PACKER_BUILDER_TYPE="
+
p
.
config
.
PackerBuilderType
copy
(
envVars
,
p
.
config
.
Vars
)
if
p
.
config
.
Inline
!=
nil
{
temp
,
err
:=
extractScript
(
p
)
if
err
!=
nil
{
ui
.
Error
(
fmt
.
Sprintf
(
"Unable to extract inline scripts into a file: %s"
,
err
))
}
scripts
=
append
(
scripts
,
temp
)
}
for
_
,
path
:=
range
scripts
{
ui
.
Say
(
fmt
.
Sprintf
(
"Provisioning with shell script: %s"
,
path
))
log
.
Printf
(
"Opening %s for reading"
,
path
)
f
,
err
:=
os
.
Open
(
path
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Error opening shell script: %s"
,
err
)
}
defer
f
.
Close
()
// Create environment variables to set before executing the command
flattendVars
,
err
:=
p
.
createFlattenedEnvVars
()
if
err
!=
nil
{
return
err
}
// Compile the command
p
.
config
.
ctx
.
Data
=
&
ExecuteCommandTemplate
{
Vars
:
flattendVars
,
Path
:
p
.
config
.
RemotePath
,
}
command
,
err
:=
interpolate
.
Render
(
p
.
config
.
ExecuteCommand
,
&
p
.
config
.
ctx
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Error processing command: %s"
,
err
)
}
// Upload the file and run the command. Do this in the context of
// a single retryable function so that we don't end up with
// the case that the upload succeeded, a restart is initiated,
// and then the command is executed but the file doesn't exist
// any longer.
var
cmd
*
packer
.
RemoteCmd
err
=
p
.
retryable
(
func
()
error
{
if
_
,
err
:=
f
.
Seek
(
0
,
0
);
err
!=
nil
{
return
err
}
if
err
:=
comm
.
Upload
(
p
.
config
.
RemotePath
,
f
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Error uploading script: %s"
,
err
)
}
cmd
=
&
packer
.
RemoteCmd
{
Command
:
command
}
return
cmd
.
StartWithUi
(
comm
,
ui
)
})
if
err
!=
nil
{
return
err
}
// Close the original file since we copied it
f
.
Close
()
if
cmd
.
ExitStatus
!=
0
{
return
fmt
.
Errorf
(
"Script exited with non-zero exit status: %d"
,
cmd
.
ExitStatus
)
}
}
return
nil
}
func
(
p
*
Provisioner
)
Cancel
()
{
// Just hard quit. It isn't a big deal if what we're doing keeps
// running on the other side.
os
.
Exit
(
0
)
}
// retryable will retry the given function over and over until a
// non-error is returned.
func
(
p
*
Provisioner
)
retryable
(
f
func
()
error
)
error
{
startTimeout
:=
time
.
After
(
p
.
config
.
StartRetryTimeout
)
for
{
var
err
error
if
err
=
f
();
err
==
nil
{
return
nil
}
// Create an error and log it
err
=
fmt
.
Errorf
(
"Retryable error: %s"
,
err
)
log
.
Printf
(
err
.
Error
())
// Check if we timed out, otherwise we retry. It is safe to
// retry since the only error case above is if the command
// failed to START.
select
{
case
<-
startTimeout
:
return
err
default
:
time
.
Sleep
(
retryableSleep
)
}
}
}
func
(
p
*
Provisioner
)
createFlattenedEnvVars
()
(
flattened
string
,
err
error
)
{
flattened
=
""
envVars
:=
make
(
map
[
string
]
string
)
// Always available Packer provided env vars
envVars
[
"PACKER_BUILD_NAME"
]
=
p
.
config
.
PackerBuildName
envVars
[
"PACKER_BUILDER_TYPE"
]
=
p
.
config
.
PackerBuilderType
// Split vars into key/value components
for
_
,
envVar
:=
range
p
.
config
.
Vars
{
keyValue
:=
strings
.
Split
(
envVar
,
"="
)
if
len
(
keyValue
)
!=
2
{
err
=
errors
.
New
(
"Shell provisioner environment variables must be in key=value format"
)
return
}
envVars
[
keyValue
[
0
]]
=
keyValue
[
1
]
}
// Create a list of env var keys in sorted order
var
keys
[]
string
for
k
:=
range
envVars
{
keys
=
append
(
keys
,
k
)
}
sort
.
Strings
(
keys
)
// Re-assemble vars using OS specific format pattern and flatten
for
_
,
key
:=
range
keys
{
flattened
+=
fmt
.
Sprintf
(
p
.
config
.
EnvVarFormat
,
key
,
envVars
[
key
])
}
return
}
provisioner/windows-shell/provisioner_test.go
0 → 100644
View file @
339a4ccd
This diff is collapsed.
Click to expand it.
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